13

我正在尝试在一个简单的 Scala 程序中重新创建 Hadoop 的字数图/减少逻辑以进行学习

这是我到目前为止所拥有的

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)

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

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

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

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

4

5 回答 5

13

您可以使用Scalaz 的 |+|运算符,因为Maps它们是Semigroup类型类的一部分:

|+|运算符是 Monoidmappend函数(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)

因此,在您的情况下,而不是创建 a List[(String,Int)],创建 a List[Map[String,Int]],然后对它们求和:

val mapped = input.flatMap(_.split(" ").map(word => Map(word -> 1)))
mapped.suml
于 2012-12-14T01:17:02.373 回答
8

您可以使用返回 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))
})  
于 2012-12-13T21:13:23.500 回答
6

如果我错了,请纠正我,但这个怎么样:

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))
于 2016-01-08T10:50:04.313 回答
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)
于 2012-12-14T11:19:40.777 回答
2

开始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 元素本身(身份)(MapReduce 的组部分)

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

  • reduces 值在一组值 ( _ + _) 中,通过对它们求和(减少 groupMap Reduce的一部分)。

这是可以通过以下方式翻译的一次性版本

words.groupBy(identity).mapValues(_.map(_ => 1).reduce(_ + _))
于 2018-10-09T18:26:22.807 回答