【问题标题】:Idiomatic way to reduce a list of pairs to a map of keys and their aggregated count?将对列表减少为键映射及其聚合计数的惯用方法?
【发布时间】:2012-12-13 21:03:51
【问题描述】:

我正在尝试在一个简单的 Scala 程序中重新创建 Hadoop 的 word count map / reduce 逻辑以进行学习

这是我目前所拥有的

val words1 = "Hello World Bye World"      
val words2 = "Hello Hadoop Goodbye Hadoop"

val input = List(words1,words2)           
val mapped = input.flatMap(line=>line.split(" ").map(word=>word->1))
    //> mapped  : List[(String, Int)] = List((Hello,1), (World,1), (Bye,1), 
    //                                       (World,1), (Hello,1), (Hadoop,1), 
    //                                       (Goodbye,1), (Hadoop,1))

mapped.foldLeft(Map[String,Int]())((sofar,item)=>{
    if(sofar.contains(item._1)){
        sofar.updated(item._1, item._2 + sofar(item._1))
    }else{
        sofar + item
    }
})                              
    //>Map(Goodbye -> 1, Hello -> 2, Bye -> 1, Hadoop -> 2, World -> 2)

这似乎可行,但我确信有一种更惯用的方式来处理 reduce 部分 (foldLeft)

我在考虑也许是多图,但我觉得 Scala 有办法轻松做到这一点

有吗?例如一种添加到地图的方法,如果键存在,而不是替换它,而是将值添加到现有值中。我确定我在某处看到过这个问题,但找不到它,也没有答案。

我知道groupBy 可能是在现实世界中实现它的方式,但我正在尝试尽可能接近上面链接中的原始映射/减少逻辑来实现它。

【问题讨论】:

    标签: scala


    【解决方案1】:

    您可以使用Scalaz's |+| 运算符,因为MapsSemigroup 类型类的一部分:

    |+| 运算符是 Monoid mappend 函数(Monoid 是可以“添加”在一起的任何“东西”。许多东西可以像这样添加在一起:字符串、整数、映射、列表、选项等. 一个例子:

    scala> import scalaz._
    import scalaz._
    
    scala> import Scalaz._
    import Scalaz._
    
    scala> val map1 = Map(1 -> 3 , 2 -> 4)
    map1: scala.collection.immutable.Map[Int,Int] = Map(1 -> 3, 2 -> 4)
    
    scala> val map2 = Map(1 -> 1, 3 -> 6)
    map2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 1, 3 -> 6)
    
    scala> map1 |+| map2
    res2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 4, 3 -> 6, 2 -> 4)
    

    所以在你的情况下,而不是创建一个List[(String,Int)],创建一个List[Map[String,Int]],然后将它们相加:

    val mapped = input.flatMap(_.split(" ").map(word => Map(word -> 1)))
    mapped.suml
    

    【讨论】:

    • 不错,还没玩过scalaz。用外行的话来说,这是“Scala 的番石榴”吗?
    • 我想是这样的。 Scalaz 基本上是一个“pimps”(使用 Pimp my Library 模式)Scala 的默认库的库,其中包含一些从 Haskell 和类别理论中汲取思想的选择组件。它旨在让 Scala 以比默认库允许的更“经典功能”的风格使用。所以你会得到状态单子、应用函子、IO 抽象等东西。这些东西需要一段时间才能理解,但真正有用。
    • @EranMedan 我会说 Haskell for Scala
    • Scalaz +100。越早学习,生活就越好!
    • 而不是 mapped.reduce(_ |+| _) 您可以调用 mapped.suml(无参数)并获得相同的结果。
    【解决方案2】:

    您可以使用返回 0 作为默认值的映射。地图提供默认值:

    def withDefaultValue[B1 >: B](d: B1): Map[A, B1]
    

    具有给定默认值的同一地图:

    val emptyMap = Map[String,Int]().withDefaultValue(0)
    mapped.foldLeft(emptyMap)((sofar,item) => {
        sofar.updated(item._1, item._2 + sofar(item._1))
    })  
    

    【讨论】:

    • 这很好用。我一厢情愿地认为sofar.addgregate(item, _ + _) 形式中有一些神奇的方法,并避免使用所有_1 _2 符号,但我想这已经足够了
    • 我记得读过一篇关于幺半群、地图和 scalaz 的博客。我认为可以使用 |+| 添加到地图中运营商...
    • 旁白:withDefaultValue 的替代方法是使用 sofar(item._1) 代替 sofar.get(item._1).getOrElse(0)
    【解决方案3】:

    如果我错了,请纠正我,但如何:

    val w = words.groupBy(_.toString).map(x => (x._1,x._2.size)).toList
    

    假设 words 是单词列表:

    val words1 = "Hello World Bye World"
    val words2 = "Hello Hadoop Goodbye Hadoop"
    val words = words1.split(" ") ++ words2.split(" ")
    val w = words.groupBy(_.toString).map(x => (x._1,x._2.size)).toList
    //List((Goodbye,1), (Hello,2), (Bye,1), (Hadoop,2), (World,2))
    

    【讨论】:

      【解决方案4】:

      另一个版本:

       val words1 = "Hello World Bye World"             
      //> words1  : java.lang.String = Hello World Bye World
       val words2 = "Hello Hadoop Goodbye Hadoop"       
      //> words2  : java.lang.String = Hello Hadoop Goodbye Hadoop
      
       val words = words1.split(" ") ++ words2.split(" ")
      //> words  : Array[java.lang.String] = Array(Hello, World, Bye, World, Hello, Hadoop, Goodbye, Hadoop)
      
       words.map(m => (m, (0 /: words)
         ((x, y) => if (y == m) x + 1 else x))).
           toList.distinct.toMap
       //> res0: scala.collection.immutable.Map[java.lang.String,Int] = Map(Goodbye -> 1, Hello -> 2, Bye -> 1, Hadoop -> 2, World -> 2)
      

      【讨论】:

        【解决方案5】:

        Scala 2.13 开始,大多数集合都提供groupMapReduce 方法,可以看作是Hadoop's map/reduce 逻辑的近似:

        val words = List(words1, words2).flatMap(_.split(" "))
        
        words.groupMapReduce(identity)(_ => 1)(_ + _)
        // immutable.Map[String,Int] = HashMap(Goodbye -> 1, Hello -> 2, Bye -> 1, Hadoop -> 2, World -> 2)
        

        这个:

        • 拆分和合并来自 2 个输入列表的单词

        • groups 元素本身(身份)(groupMapReduce 的组部分)

        • maps 每个分组值出现为 1(映射组的一部分MapReduce)

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

        这是one-pass version 可以翻译的内容:

        words.groupBy(identity).mapValues(_.map(_ => 1).reduce(_ + _))
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-31
          相关资源
          最近更新 更多