给定一个组ID /值元组的序列,很容易计算组总数(与使用C#和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)
但是作为F#的新手,我无法看到与列表相同的方法。我是否必须使用可变变量,或者是否有一种功能性的方法来对列表进行相同的操作?
答案 0 :(得分:8)
内置List.groupBy
。许多F#内置类型具有分配了所述函数的seq版本的函数。例如来自list.fs
let inline sumBy f (list : list<_>) = Seq.sumBy f list
我很确定F#的设计者为了保持一致性以及为了DRY而省略了什么,需要进行多次讨论。我个人希望他们坚持使用DRY。
如果你想制作自己的“功能性”List.group我会使用地图和列表。
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]
答案 1 :(得分:7)
虽然gradbot的解决方案没有任何问题,但我只是保持简单,并在需要时使用Seq.toList
将序列转换回列表。所以你可以将你的定义重写为:
let groupsums =
items
|> Seq.groupBy fst
|> Seq.toList
|> List.map (fun (_,s) -> Seq.sumBy snd s)
答案 2 :(得分:7)
虽然我会使用kvb的建议,但如果您打算使用自己的建议,我建议使用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
或者:
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