【问题标题】:efficient way to create map of lists in functional style以功能样式创建列表映射的有效方法
【发布时间】:2014-03-05 05:00:53
【问题描述】:

给定一个数据集,例如一个可能如下所示的 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 秒内完成。

我认为 Lee 提供的答案是最一般的 FP 答案,但运行时间约为 4 秒。

Daniel 给出的答案运行时间约为 1.55 秒。

最后 jbtule 给出的答案在 ~0.26 范围内运行。 (我觉得它击败了命令式方法非常有趣)

我使用'System.Diagnostics.Stopwatch()' 进行计时,代码在.Net 4.5 中作为F# 3.0 执行

EDIT2:修复了命令式 f# 代码中的愚蠢错误,并确保它使用与其他解决方案相同的列表

【问题讨论】:

  • 在速度方面,它更多的是关于数据结构,而不是创建它的少量代码。 Map(平衡树)、Dictionary(可变哈希表)、Lookup(非可变哈希表),它们都有不同的特征,尤其是与您放入其中的数据大小有关。此外,您不应该只考虑创建时间,因为很可能会在更快的创建和更慢的值查找之间进行权衡。
  • 我实际上假设 Maps 在 FP 中的哈希表..很高兴知道它们实际上是平衡树..所以显然 Maps 必须具有较慢的查找时间,O(log n) 或类似的东西。
  • 您可以进一步改进命令式版本:dotnetfiddle.net/T3DUDV

标签: f#


【解决方案1】:
[
  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

【讨论】:

  • 如果你对序列图没问题,那么列表理解可以缩短为Seq.map snd ys
  • 我真的很喜欢这个解决方案的简洁性......但是,这不会潜在地迭代数据集 3 次吗?
【解决方案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 值以相反的顺序添加到列表中

【讨论】:

  • 我想知道这个解决方案是否会给垃圾收集器带来很大的压力,-如果是为 csv 文件中的每个条目创建一个新的 Map 对象..?
  • @Mitzh - 我不这么认为 - 地图可能实现为平衡二叉树,其中每个新地图与旧地图共享节点。
  • 我刚刚在一个相对较大的 csv 文件上测试了这个解决方案和 Daniels,并用 'System.Diagnostics.Stopwatch()' 测量了时间。我预计这个会是最快的,但是我得到的结果表明,这个实际上比 Daniels 慢。(这个解决方案约 4 秒,丹尼尔斯约 1.5 秒)
  • @Mitzh - 如果您查看groupBy 的实现,它在内部使用可变列表和字典,因此速度更快也就不足为奇了。其他答案更符合 F# 的习惯,但这展示了更直接的“功能风格”。
  • @Mitzh - F#编译器是开源的,你可以在github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/…找到groupBy的实现
【解决方案3】:

在 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

【讨论】:

  • 这与使用Seq.groupBy基本相同。
  • 但它是一次性完成 Hashtable 和元素转换。
  • 啊,是的,很好。
【解决方案4】:

为了好玩,使用查询表达式的实现:

let res =
    query { for (k, v) in data do
            groupValBy v k into g
            select (g.Key, List.ofSeq g) }
    |> Map.ofSeq

【讨论】:

    猜你喜欢
    • 2021-04-25
    • 2023-04-09
    • 2023-03-15
    • 2023-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-18
    相关资源
    最近更新 更多