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

后端 未结 3 510
旧巷少年郎
旧巷少年郎 2021-02-09 04:50

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\"         


        
相关标签:
3条回答
  • 2021-02-09 05:26

    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]
    
    0 讨论(0)
  • 2021-02-09 05:40

    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)
    
    0 讨论(0)
  • 2021-02-09 05:44

    Although I would use kvb's suggestion, if you're going to roll your own, I suggest using Dictionary instead of Map. In my testing it was at least 400% faster.

    let groupBy f (list:list<_>) =
      let dict = Dictionary()
      for v in list do
        let k = f v
        match dict.TryGetValue(k) with
        | true, l -> dict.[k] <- v :: l
        | _ -> dict.Add(k, [v])
      dict |> Seq.map (|KeyValue|) |> Seq.toList
    

    Or:

    let groupSumBy (list:list<_>) =
      let dict = Dictionary()
      for k, v in list do
        match dict.TryGetValue(k) with
        | true, n -> dict.[k] <- v + n
        | _ -> dict.Add(k, v)
      dict |> Seq.map (|KeyValue|) |> Seq.toList
    

    By ref version:

    let groupSumBy (list:list<_>) =
      let dict = Dictionary()
      let mutable n = 0
      for k, v in list do
        match dict.TryGetValue(k, &n) with
        | true -> dict.[k] <- v + n
        | false -> dict.Add(k, v)
      dict |> Seq.map (|KeyValue|) |> Seq.toList
    
    0 讨论(0)
提交回复
热议问题