给定数据集,例如可能如下所示的CSV文件:
x,y
1,2
1,5
2,1
2,2
1,1
...
我希望创建一个包含给定x的y的列表地图...结果可能如下所示:
{1:[2,5,1], 2:[1,2]}
在python中,这可以直接以强制方式执行..并且可能看起来有点像这样:
d = defaultdict(list)
for x,y in csv_data:
d[x].append(y)
您如何使用F#中的函数式编程技术实现相同的目标? 是否有可能像在给定的python示例中那样使用简单,有效和简洁的(和可读),只使用功能样式?,或者你必须回归命令式编程风格可变数据结构..?
注意:这不是一个家庭作业,只是我试图围绕功能编程
编辑:我的结论基于迄今为止的答案
我尝试在一个相对较大的csv文件上计算每个提供的答案,只是为了获得性能的感觉。此外,我用命令式方法做了一个小测试:
let res = new Dictionary<string, List<string>>()
for row in l do
if (res.ContainsKey(fst row) = false) then
res.[fst row] <- new List<string>()
res.[fst row].Add(snd row)
命令式方法在~0.34秒内完成。
我认为李提供的答案是最普遍的FP,但运行时间约为4秒。
Daniel给出的答案在~1.55秒内完成。
最后,jbtule给出的答案大约是0.26。 (我发现它非常有趣,它击败了命令式方法)
我使用'System.Diagnostics.Stopwatch()'进行计时,代码在.Net 4.5中作为F#3.0执行
EDIT2:修复了命令式f#代码中的愚蠢错误,并确保它使用与其他解决方案相同的列表
答案 0 :(得分:7)
[
1,2
1,5
2,1
2,2
1,1
]
|> Seq.groupBy fst
|> Seq.map (fun (x, ys) -> x, [for _, y in ys -> y])
|> Map.ofSeq
答案 1 :(得分:2)
let addPair m (x, y) =
match Map.tryFind x m with
| Some(l) -> Map.add x (y::l) m
| None -> Map.add x [y] m
let csv (pairs : (int * int) list) = List.fold addPair Map.empty pairs
请注意,这会以相反的顺序将y
值添加到列表中
答案 2 :(得分:2)
在F#中使用LINQ,LINQ功能正常。
open System.Linq
let data =[
1,2
1,5
2,1
2,2
1,1
]
let lookup = data.ToLookup(fst,snd)
lookup.[1] //seq [2;5;1]
lookup.[2] //seq [1;2
答案 3 :(得分:1)
为了好玩,使用查询表达式的实现:
let res =
query { for (k, v) in data do
groupValBy v k into g
select (g.Key, List.ofSeq g) }
|> Map.ofSeq