SantiClaus
SantiClaus

Reputation: 726

How to sortby a variable ascending in F#?

How would I sort a list by a variable but ascending?

Something like:

Data |> List.sortBy(fun t -> t.Date,ascending(t.Value))

The above is an example, I know that this will not work if run.

Upvotes: 0

Views: 507

Answers (2)

AMieres
AMieres

Reputation: 5004

You can easily sort by multiple keys, ascending, descending, or any other complex order, with List.sortWith and the power of function composition:

All you need is a couple of helper functions and an operator:

let asc   f    a b = compare (f a) (f b)
let desc  f    a b = compare (f b) (f a)
let (&>) c1 c2 a b = match c1 a b with 0 -> c2 a b | r -> r

Both asc and desc receive a key-retrieve-function of type 'T->'K and call the generic function compare to sort in ascending or descending order. The operator &> lets you compose them to sort by as many keys as you like. And since you can also add your custom comparers, any kind of sorting is possible with this technique:

let ls = [ "dd"; "a"; "b"; "c"; "aa"; "bb"; "cc"]

ls |> List.sortWith(desc Seq.length &> 
                    asc  id)        
// result = ["aa"; "bb"; "cc"; "dd"; "a"; "b"; "c"]

ls |> List.sortWith( asc Seq.length &> 
                     desc id) 
// result = ["c"; "b"; "a"; "dd"; "cc"; "bb"; "aa"]

Your example would look like this:

Data |> List.sortWith( desc (fun t -> t.Date)  &> 
                        asc (fun t -> t.Value))

Upvotes: 1

Tomas Petricek
Tomas Petricek

Reputation: 243061

Based on your example, it looks like you want to use multiple sorting keys and some of them should be in the ascending order while others in descending order. I think this is a scenario that has not been answered by any of the other questions.

In general, you can use multiple sorting keys in F# by using tuples. F# has functions List.sortBy and List.sortByDescending which give you the two possible orders:

data |> Seq.sortByDescending (fun x -> x.FirstKey, x.SecondKey)

However, this way the sort order for both of the keys will be the same. There is no easy way to use one key in one order and another key in another order. In many cases, you could just use numerical minus and do something like:

data |> Seq.sortByDescending (fun x -> x.FirstKey, -x.SecondKey)

This is not entirely bullet-proof because of MaxInt values, but it will probably often work. In F# query expressions (which are inspired by how LINQ works), you can use multiple sorting keys using sortBy and thenBy (or sortByDescending and thenByDescending):

query {
  for x in data do
  sortByDescending x.FirstKey
  thenBy x.SecondKey }

Here, the first key will be used for descending sort and, when there are multiple items with the same FirstKey, the second key will be used for ascending sort within that group. I suspect this is probably what you need in the general case - but it's a bit unfortunate there is no nice way of writing this with the pipeline syntax.

Upvotes: 5

Related Questions