以功能样式创建列表映射的有效方法

时间:2014-03-04 17:01:06

标签: f#

给定数据集,例如可能如下所示的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#代码中的愚蠢错误,并确保它使用与其他解决方案相同的列表

4 个答案:

答案 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