Home

F# Lists: Sorting Items

   

Introduction

Sorting the items of a list consists of re-arranging them in order. If the items are of a primitive type, you can arrange them in alphabetical, numerical, or chronological order. If the items are based on a class, you must implement the IComparable interface that is equipped with a method named CompareTo.

To let you arrange the items of a list, the List module is equipped with a function named sort. Its signature is:

List.sort : 'T list -> 'T list

Here is an example:

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic
open System.Runtime.Serialization.Formatters.Binary

type StoreItem = {
    ItemNumber  : int
    Category    : string
    SubCategory : string
    ItemName    : string
    ItemSize    : string
    UnitPrice   : float
    DaysInStore : int
    Status      : string }

type SaleItem(number, category, subcat, name, size, price, days) =
    let nbr = ref number
    let cat = ref category
    let sub = ref subcat
    let nm  = ref name
    let sz  = ref size
    let prc = ref price
    let ds  = ref days
    
    member this.ItemNumber  with get() = !nbr and set(value) = nbr := value
    member this.Category    with get() = !cat and set(value) = cat := value
    member this.SubCategory with get() = !sub and set(value) = sub := value
    member this.ItemName    with get() = !nm  and set(value) = nm  := value
    member this.ItemSize    with get() = !sz  and set(value) = sz  := value
    member this.UnitPrice   with get() = !prc and set(value) = prc := value    
    member this.DaysInStore with get() = !ds  and set(value) = ds  := value
    
    member this.DiscountRate with get() =
                                    if !ds >= 60 then 50.00
                                    elif !ds >= 30 then 35.00
                                    elif !ds >= 15 then 10.00
                                    else 0.00
    member this.DiscountAmount with get() = !prc * this.DiscountRate / 100.00    
    member this.MarkedPrice    with get() = this.UnitPrice - this.DiscountAmount

    interface IComparable with
        member this.CompareTo(obj : Object) =
            match obj with
            | :? SaleItem as si -> compare this.UnitPrice si.UnitPrice
            | _ -> 0

    override this.GetHashCode() = this.GetHashCode()
    override this.Equals(obj) =
            match obj with
            | :? SaleItem as si -> this.ItemNumber = si.ItemNumber
            | _ -> false

    new() = SaleItem(0, "", "", "", "", 0.00, 0)
   
let mutable storeItems = new List<StoreItem>()
let mutable salesItems = []

let departmentStore = new Form(ClientSize = new Size(865, 390),
                               MaximizeBox = false, Text = "Department Store", 
                               StartPosition = FormStartPosition.CenterScreen)

let lvwStoreItems : ListView = new ListView(GridLines = true, View = View.Details, FullRowSelect = true, Location = new Point(12, 12), Size = new System.Drawing.Size(840, 330))
let mutable col = lvwStoreItems.Columns.Add("Item #", 60)
col <- lvwStoreItems.Columns.Add("Category", 65)
col <- lvwStoreItems.Columns.Add("Sub-Category", 80)
col <- lvwStoreItems.Columns.Add("Item Name", 220)
col <- lvwStoreItems.Columns.Add("Item Size", 60)
col <- lvwStoreItems.Columns.Add("Unit Price", 60, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Days in Store", 75, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Disc Rate", 65, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Disc Amt", 60, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Marked Price", 75, HorizontalAlignment.Right)

departmentStore.Controls.Add lvwStoreItems(**)

let showStoreItems items =
    lvwStoreItems.Items.Clear()

    List.iter (fun (item : SaleItem) ->
        let mutable lviStoreItem = new ListViewItem(sprintf "%i" item.ItemNumber)
        lviStoreItem.SubItems.Add item.Category |> ignore
        lviStoreItem.SubItems.Add item.SubCategory |> ignore
        lviStoreItem.SubItems.Add item.ItemName |> ignore
        lviStoreItem.SubItems.Add item.ItemSize |> ignore
        lviStoreItem.SubItems.Add(sprintf "%0.02f" item.UnitPrice) |> ignore
        lviStoreItem.SubItems.Add(sprintf "%i" item.DaysInStore) |> ignore
        if item.DiscountRate <= 0.00 then
            lviStoreItem.SubItems.Add "" |> ignore
        else
            lviStoreItem.SubItems.Add(sprintf "%0.0f%c" item.DiscountRate '%') |> ignore
        if item.DiscountRate <= 0.00 then
            lviStoreItem.SubItems.Add "" |> ignore
        else
            lviStoreItem.SubItems.Add(sprintf "%0.02f" item.DiscountAmount) |> ignore
        lviStoreItem.SubItems.Add(sprintf "%0.02f" item.MarkedPrice) |> ignore
        lvwStoreItems.Items.Add lviStoreItem |> ignore) items

let departmentStoreLoad _ =
    let bfStoreItems = new BinaryFormatter()
    let strStoreItemsFile : string =  @"C:\Fun Department Store\StoreItems.sis"

    use fsStoreItems = new FileStream(strStoreItemsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
    storeItems <- bfStoreItems.Deserialize(fsStoreItems) :?> List<StoreItem>
    fsStoreItems.Close()

    salesItems <- [ for item in storeItems do if item.Status = "Flawless" then yield new SaleItem(item.ItemNumber, item.Category, item.SubCategory, item.ItemName, item.ItemSize, item.UnitPrice, item.DaysInStore) ]
    showStoreItems salesItems
departmentStore.Load.Add departmentStoreLoad

let btnShowAllItems = new Button(Location = new Point(12, 355), Width = 150, Text = "Show all Items")
departmentStore.Controls.Add btnShowAllItems

let btnShowAllItemsClick _ = 
    showStoreItems salesItems
btnShowAllItems.Click.Add btnShowAllItemsClick

let btnWomenItems = new Button(Location = new Point(170, 355), Width = 150, Text = "Show Women Items")
departmentStore.Controls.Add btnWomenItems

let btnWomenItemsClick _ =
    let womenItems = salesItems |> List.choose (fun (item : SaleItem) ->
            match item.Category with
            | "Women" -> Some(item)
            | _ -> None)
    showStoreItems womenItems
btnWomenItems.Click.Add btnWomenItemsClick

let btnSortByUnitPrice = new Button(Location = new Point(330, 355), Width = 170, Text = "Sort by Unit Price")
departmentStore.Controls.Add btnSortByUnitPrice

let btnSortByUnitPriceClick _ =
    let sales = List.sort salesItems
    showStoreItems sales
btnSortByUnitPrice.Click.Add btnSortByUnitPriceClick

let btnIncreasePricesBy10Percent = new Button(Location = new Point(330, 355), Width = 170, Text = "Increase Unit Prices by 10%")
departmentStore.Controls.Add btnIncreasePricesBy10Percent

let btnIncreasePricesBy10PercentClick _ =
    let sales = List.map (fun (item : SaleItem) -> new SaleItem(item.ItemNumber, item.Category, item.SubCategory, item.ItemName, item.ItemSize,
                                                                item.UnitPrice + (item.UnitPrice * 0.10), item.DaysInStore)
                ) salesItems
    showStoreItems sales
btnIncreasePricesBy10Percent.Click.Add btnIncreasePricesBy10PercentClick

let btnClose = new Button(Left = 780, Top = 355, Text = "Close")
let btnCloseClick e =
    departmentStore.Close()
btnClose.Click.Add btnCloseClick
departmentStore.Controls.Add btnClose

[<EntryPoint>]
let main argv =
    Application.Run departmentStore
    0

This would produce:

Introduction to Sorting Items

Introduction to Sorting Items

     
 

Sorting Items By a Condition

The List.sort() function is used to simply arrange all records of a list. If you want to apply a condition by which to sort records, you can use the sortBy() function of the List module. Its signature is:

List.sortBy : ('T -> 'Key) -> 'T list -> 'T list

This function takes two arguments. The first arguments specifies a condition by which to sort. Only the items that respond to the condition are sorted. The other items in their original positions. Here is an example:

open System
open System.IO
open System.Drawing
open System.Windows.Forms
open System.Collections.Generic
open System.Runtime.Serialization.Formatters.Binary

type StoreItem = {
    ItemNumber  : int
    Category    : string
    SubCategory : string
    ItemName    : string
    ItemSize    : string
    UnitPrice   : float
    DaysInStore : int
    Status      : string }

type SaleItem(number, category, subcat, name, size, price, days) =
    let nbr = ref number
    let cat = ref category
    let sub = ref subcat
    let nm  = ref name
    let sz  = ref size
    let prc = ref price
    let ds  = ref days
    
    member this.ItemNumber  with get() = !nbr and set(value) = nbr := value
    member this.Category    with get() = !cat and set(value) = cat := value
    member this.SubCategory with get() = !sub and set(value) = sub := value
    member this.ItemName    with get() = !nm  and set(value) = nm  := value
    member this.ItemSize    with get() = !sz  and set(value) = sz  := value
    member this.UnitPrice   with get() = !prc and set(value) = prc := value    
    member this.DaysInStore with get() = !ds  and set(value) = ds  := value
    
    member this.DiscountRate with get() =
                                    if !ds >= 60 then 50.00
                                    elif !ds >= 30 then 35.00
                                    elif !ds >= 15 then 10.00
                                    else 0.00
    member this.DiscountAmount with get() = !prc * this.DiscountRate / 100.00    
    member this.MarkedPrice    with get() = this.UnitPrice - this.DiscountAmount

    interface IComparable with
        member this.CompareTo(obj : Object) =
            match obj with
            | :? SaleItem as si -> compare this.UnitPrice si.UnitPrice
            | _ -> 0
    override this.GetHashCode() = this.GetHashCode()
    override this.Equals(obj) =
            match obj with
            | :? SaleItem as si -> this.ItemNumber = si.ItemNumber
            | _ -> false

    new() = SaleItem(0, "", "", "", "", 0.00, 0)
   
let mutable storeItems = new List<StoreItem>()
let mutable salesItems = []

let departmentStore = new Form(ClientSize = new Size(865, 390),
                               MaximizeBox = false, Text = "Department Store", 
                               StartPosition = FormStartPosition.CenterScreen)

let lvwStoreItems : ListView = new ListView(GridLines = true, View = View.Details, FullRowSelect = true, Location = new Point(12, 12), Size = new System.Drawing.Size(840, 330))
let mutable col = lvwStoreItems.Columns.Add("Item #", 60)
col <- lvwStoreItems.Columns.Add("Category", 65)
col <- lvwStoreItems.Columns.Add("Sub-Category", 80)
col <- lvwStoreItems.Columns.Add("Item Name", 220)
col <- lvwStoreItems.Columns.Add("Item Size", 60)
col <- lvwStoreItems.Columns.Add("Unit Price", 60, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Days in Store", 75, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Disc Rate", 65, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Disc Amt", 60, HorizontalAlignment.Right)
col <- lvwStoreItems.Columns.Add("Marked Price", 75, HorizontalAlignment.Right)

departmentStore.Controls.Add lvwStoreItems(**)

let showStoreItems items =
    lvwStoreItems.Items.Clear()

    List.iter (fun (item : SaleItem) ->
        let mutable lviStoreItem = new ListViewItem(sprintf "%i" item.ItemNumber)
        lviStoreItem.SubItems.Add item.Category |> ignore
        lviStoreItem.SubItems.Add item.SubCategory |> ignore
        lviStoreItem.SubItems.Add item.ItemName |> ignore
        lviStoreItem.SubItems.Add item.ItemSize |> ignore
        lviStoreItem.SubItems.Add(sprintf "%0.02f" item.UnitPrice) |> ignore
        lviStoreItem.SubItems.Add(sprintf "%i" item.DaysInStore) |> ignore
        if item.DiscountRate <= 0.00 then
            lviStoreItem.SubItems.Add "" |> ignore
        else
            lviStoreItem.SubItems.Add(sprintf "%0.0f%c" item.DiscountRate '%') |> ignore
        if item.DiscountRate <= 0.00 then
            lviStoreItem.SubItems.Add "" |> ignore
        else
            lviStoreItem.SubItems.Add(sprintf "%0.02f" item.DiscountAmount) |> ignore
        lviStoreItem.SubItems.Add(sprintf "%0.02f" item.MarkedPrice) |> ignore
        lvwStoreItems.Items.Add lviStoreItem |> ignore) items

let departmentStoreLoad _ =
    let bfStoreItems = new BinaryFormatter()
    let strStoreItemsFile : string =  @"C:\Fun Department Store\StoreItems.sis"

    use fsStoreItems = new FileStream(strStoreItemsFile, FileMode.Open, FileAccess.Read, FileShare.Read)
    storeItems <- bfStoreItems.Deserialize(fsStoreItems) :?> List<StoreItem>
    fsStoreItems.Close()

    salesItems <- [ for item in storeItems do if item.Status = "Flawless" then yield new SaleItem(item.ItemNumber, item.Category, item.SubCategory, item.ItemName, item.ItemSize, item.UnitPrice, item.DaysInStore) ]
    showStoreItems salesItems
departmentStore.Load.Add departmentStoreLoad

let btnShowAllItems = new Button(Location = new Point(12, 355), Width = 110, Text = "Show all Items")
departmentStore.Controls.Add btnShowAllItems

let btnShowAllItemsClick _ = 
    showStoreItems salesItems
btnShowAllItems.Click.Add btnShowAllItemsClick

let btnWomenItems = new Button(Location = new Point(130, 355), Width = 120, Text = "Show Women Items")
departmentStore.Controls.Add btnWomenItems

let btnWomenItemsClick _ =
    let womenItems = salesItems |> List.choose (fun (item : SaleItem) ->
            match item.Category with
            | "Women" -> Some(item)
            | _ -> None)
    showStoreItems womenItems
btnWomenItems.Click.Add btnWomenItemsClick

let btnSortByUnitPrice = new Button(Location = new Point(260, 355), Width = 110, Text = "Sort by Unit Price")
departmentStore.Controls.Add btnSortByUnitPrice

let btnSortByUnitPriceClick _ =
    let itemsSorted = List.sort salesItems
    showStoreItems itemsSorted
btnSortByUnitPrice.Click.Add btnSortByUnitPriceClick

let btnSortByDiscountRate = new Button(Location = new Point(380, 355), Width = 135, Text = "Sort by Discount Rate")
departmentStore.Controls.Add btnSortByDiscountRate

let btnSortByDiscountRateClick _ =
    let itemsSorted = List.sortBy (fun (item : SaleItem) -> item.DiscountRate > 0.00) salesItems
    showStoreItems itemsSorted
btnSortByDiscountRate.Click.Add btnSortByDiscountRateClick

let btnIncreasePricesBy10Percent = new Button(Location = new Point(530, 355), Width = 160, Text = "Increase Unit Prices by 10%")
departmentStore.Controls.Add btnIncreasePricesBy10Percent

let btnIncreasePricesBy10PercentClick _ =
    let sales = List.map (fun (item : SaleItem) -> new SaleItem(item.ItemNumber, item.Category, item.SubCategory, item.ItemName, item.ItemSize,
                                                                item.UnitPrice + (item.UnitPrice * 0.10), item.DaysInStore)
                ) salesItems
    showStoreItems sales
btnIncreasePricesBy10Percent.Click.Add btnIncreasePricesBy10PercentClick

let btnClose = new Button(Left = 780, Top = 355, Text = "Close")
let btnCloseClick e =
    departmentStore.Close()
btnClose.Click.Add btnCloseClick
departmentStore.Controls.Add btnClose

[<EntryPoint>]
let main argv =
    Application.Run departmentStore
    0

This would produce:

Introduction to Sorting Items

Introduction to Sorting Items

   
   
 

Home Copyright © 2009-2015, FunctionX Home