Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Group totals in F# - easy with sequences, is it possible with lists?

Tags:

f#

Given a sequence of a group id/value tuples, it was easy to calculate group totals (pretty much the same way I would do it with C# and LINQ):

let items = ["g1",5; "g2",10; "g1",20]

let groupsums = 
    items  
    |> Seq.groupBy (fun x -> fst x) 
    |> Seq.map (fun (g, s) -> Seq.fold (fun acc x -> acc + snd x) 0 s)

But being new to F#, I can't see a way to so the same with lists. Do I have to use mutable variables, or is there a functional way to do the same with lists?

like image 538
Sergey Aldoukhov Avatar asked Jul 17 '11 20:07

Sergey Aldoukhov


2 Answers

There is no built in List.groupBy. A number of F# built in types have functions that are assigned the seq version of said function. e.g. from list.fs

let inline sumBy f (list : list<_>) = Seq.sumBy f list

I'm pretty sure the designers of F# had many discussions about what to duplicate for the sake of consistency and what to omit for for sake of DRY. I personally wish they stuck with DRY.

If you want to make your own "functional" List.groupBy I'd use map and list.

let groupBy list =
    list 
    |> List.fold (fun group (g, x) -> 
        match group |> Map.tryFind g with
        | Some(s) -> group |> Map.remove g |> Map.add g (x::s)
        | None -> group |> Map.add g [x]
        ) Map.empty
    |> Map.toList 

let groupsums = groupBy >> List.map (snd >> List.sum)

You can skip keeping lists if you only need the sum.

let groupAndSumBy list =
    list 
    |> List.fold (fun group (g, x) -> 
        match group |> Map.tryFind g with
        | Some(s) -> group |> Map.remove g |> Map.add g (x + s)
        | None -> group |> Map.add g x
        ) Map.empty
    |> Map.toList
    |> List.map snd

Output

> groupsums items;;
val it : int list = [25; 10]

> groupAndSumBy items;;
val it : int list = [25; 10]
like image 175
gradbot Avatar answered Nov 14 '22 03:11

gradbot


While there's nothing wrong with gradbot's solution, I'd just keep it simple and use Seq.toList to convert sequences back to lists when desired. So you could rewrite your definition as:

let groupsums =
    items
    |> Seq.groupBy fst
    |> Seq.toList
    |> List.map (fun (_,s) -> Seq.sumBy snd s)
like image 37
kvb Avatar answered Nov 14 '22 03:11

kvb