2011-07-17 5 views
7

グループID /値のタプルのシーケンスを考えると、(ほとんど私はC#とLINQでそれを行うのと同じ方法)グループ合計を計算することは簡単でしたが:F#でのグループ合計 - シーケンスで簡単にリストで可能ですか?

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) 

しかし、F#に新しいもの、私はリストと同じようにすることはできません。可変変数を使用する必要があるのですか、リストを使って同じように機能する方法はありますか?

答えて

8

List.groupByは内蔵されていません。組み込み型F#には、その関数のseq版が割り当てられた関数があります。例えばlist.fs

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

からIは、F#の設計者は一貫性のため、何DRYのためにのために省略するために複製するかについて多くの議論があったかなり確信しています。私は個人的に彼らがドライで立ち往生したいと思います。

「機能的」なList.groupByを作成したい場合は、mapと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) 

合計が必要な場合は、リストをスキップすることができます。

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 

出力

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

> groupAndSumBy items;; 
val it : int list = [25; 10] 
+0

私は.groupByがない気づい;)しかし、おそらくF#の "魔法" のいくつかの種類を行う方法はありますグループ化を避けてください。また、「自分の.groupを作成するにはマップとリストを使用します」 - リストは何ですか?リストには.mapがありますがリストはありません –

+0

@Sergey可能な解決策を1つ追加しました。 – gradbot

+0

ありがとうございます。パフォーマンスやメモリ使用量に顕著な違いがあると思いますか? –

7

gradbotのソリューションと間違って何もありませんが、私はちょうどそれをシンプルに保つと思いますし、必要なときに戻ってリストに列を変換するSeq.toListを使用しています。ですから、あなたのような定義を書き換えることができます:あなたがあなた自身をロールバックするつもりなら、私は、KVBの提案を使用することになりますが

let groupsums = 
    items 
    |> Seq.groupBy fst 
    |> Seq.toList 
    |> List.map (fun (_,s) -> Seq.sumBy snd s) 
+0

短い表記をありがとうございますが、ここではリストは人工的です。 –

+1

@Sergey - これは本当ですが、グループ化を行うときにリストの構造を利用する実際の方法はありません。したがって、あなた自身の 'List.groupBy 'を書く代わりに' Seq.groupBy'を使うことで、 '(どちらの場合でも中間のマップ構造を使用しなければなりません)。 – kvb

+0

グループ折りたたみの結果(@ gradbot回答の2番目の部分)だけが必要な場合を除き、グループをメモリ内に保持する必要はありません。私は最高のパフォーマンス/メモリソリューションがマップにシーケンスを折り畳むだろうと思う。 –

7

、私はDictionary代わりのMapを使用することをお勧め。私のテストでは、少なくとも400%高速でした。

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 

または:refのバージョンで

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 

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 
+1

+1、あなたはTryGetValueの "by ref"バージョンを使ってさらに15%増えることができます(私は@Jon Harropがタプルパターンのマッチバージョンが私ができる余分な割り当てをいくつか見いだすのを見た'groupBy'テストを参照してください)。 –

+0

うわー、この質問は、私のようなF#noobのためにはかなり役に立ちます;)ありがとうございました! –

+0

@Stephen:私は分かりませんでした。非常に知って良い! – Daniel

関連する問題