我正在尝试在一个简单的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
可能是在现实世界中实现它的方式,但我正在尝试尽可能接近上面链接中的原始地图/减少逻辑。
答案 0 :(得分:12)
您可以使用Scalaz's |+|
运算符,因为Maps
是Semigroup类型类的一部分:
|+|
运算符是Monoid mappend
函数(Monoid是可以“添加”在一起的任何“东西”。很多东西可以像这样添加:字符串,Ints,地图,列表,选项等示例:
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
答案 1 :(得分:8)
您可以使用返回0作为默认值的地图。地图提供withDefaultValue:
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))
})
答案 2 :(得分:6)
如果我错了,请纠正我但是如何:
val w = words.groupBy(_.toString).map(x => (x._1,x._2.size)).toList
假设单词是单词列表:
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))
答案 3 :(得分: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)
答案 4 :(得分: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个输入列表中的单词
group
的元素本身(身份)(组 MapReduce的组部分)
map
将每个分组值出现的次数设为1(组 Map Reduce的映射部分)
reduce
在一组值(_ + _
)中的值相加(减少groupMap Reduce 的一部分)。
这是one-pass version可以翻译的内容:
words.groupBy(identity).mapValues(_.map(_ => 1).reduce(_ + _))