【问题标题】:Count occurrences of each element in a List[List[T]] in Scala在 Scala 中计算 List[List[T]] 中每个元素的出现次数
【发布时间】:2012-08-28 19:36:50
【问题描述】:

假设你有

val docs = List(List("one", "two"), List("two", "three"))

例如在哪里List("one", "two") 表示包含术语 "one" 和 "two" 的文档,并且您希望构建一个包含每个术语的文档频率的地图,即在这种情况下

Map("one" -> 1, "two" -> 2, "three" -> 1)

你会如何在 Scala 中做到这一点? (并且以一种有效的方式,假设数据集更大。)

我的第一个类似 Java 的想法是使用可变映射:

val freqs = mutable.Map.empty[String,Int]
for (doc <- docs)
  for (term <- doc)
    freqs(term) = freqs.getOrElse(term, 0) + 1

效果很好,但我想知道如何以更“实用”的方式做到这一点,而无需使用可变映射?

【问题讨论】:

    标签: scala scala-collections


    【解决方案1】:

    试试这个:

    scala> docs.flatten.groupBy(identity).mapValues(_.size)
    res0: Map[String,Int] = Map(one -> 1, two -> 2, three -> 1)
    

    如果您要多次访问计数,那么您应该避免使用mapValues,因为它是“惰性的”,因此会在每次访问时重新计算大小。此版本为您提供相同的结果,但不需要重新计算:

    docs.flatten.groupBy(identity).map(x => (x._1, x._2.size))
    

    identity 函数仅表示x =&gt; x

    【讨论】:

    • 不错。不过,它似乎确实比使用可变映射慢,只有大约 10k 个术语。转换集合 3 倍的成本?
    • 是的,它很好用而且很实用,但是复制所有的数据并不能提高效率。可变 Map 版本不会浪费很多时间。
    • +1 教我mapValues 在每次遍历时重新计算地图。但在这种情况下,带有foldLeft 的表达式应该比groubBy 执行得更好。
    • 完美答案。根据我对用例的基准测试,这与手动可变映射实现相比实际上快了 4 倍。速度对我的使用很重要,因为它位于处理 TB 数据的 mapreduce 作业中。此外,随着时间的推移,Scala 编译器和 JVM 会变得更好,这将自动优化,而手动实现则不会。
    • @Val “毫无疑问”?好吧,我刚刚检查了l.groupBy(identity).mapValues(_.size)l.foldLeft(Map.empty[Int, Int].withDefaultValue(0))((m, x) =&gt; m + (x -&gt; (1 + m(x)))),其中l(1 to 10000).map(_ =&gt; scala.util.Random.nextInt(100)).toList。在 5000 次试验中,groupBy 方法耗时 2510 毫秒,而foldLeft 方法耗时 8349 毫秒。我已经用许多其他发行版和不同的机器重复了这个实验。不管怎样,如果你真的看一下groupBy 的实现,你就会明白为什么:)
    【解决方案2】:
    docs.flatten.foldLeft(new Map.WithDefault(Map[String,Int](),Function.const(0))){
      (m,x) => m + (x -> (1 + m(x)))}
    

    什么是火车残骸!

    [编辑]

    啊,这样更好!

    docs.flatten.foldLeft(Map[String,Int]() withDefaultValue 0){
      (m,x) => m + (x -> (1 + m(x)))}
    

    【讨论】:

    • 可以缩短地图初始化:docs.flatten.foldLeft( Map[String,Int]() withDefaultValue 0 ){ (m,x) =&gt; ... }
    • 它似乎比groupBy 快​​,所以将其标记为已接受。但两个答案都很有趣。
    【解决方案3】:

    Scala 2.13开始,在flatten列表列表之后,我们可以使用groupMapReduce,这是groupBy/mapValues的一次性替代方案:

    // val docs = List(List("one", "two"), List("two", "three"))
    docs.flatten.groupMapReduce(identity)(_ => 1)(_ + _)
    // Map[String,Int] = Map("one" -> 1, "three" -> 1, "two" -> 2)
    

    这个:

    • flattens Lists 的 List 作为 List

    • groups 列表元素 (identity)(groupMapReduce 的组部分)

    • maps 每个分组值出现为 1 (_ =&gt; 1)(映射组的一部分MapReduce)

    • 将一组值 (_ + _) 中的reduces 值相加(减少 groupMap 的一部分Reduce)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-07
      • 2010-10-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-19
      • 1970-01-01
      相关资源
      最近更新 更多