最好的方法来合并两个地图和总结相同的密钥的值?

val map1 = Map(1 -> 9 , 2 -> 20) val map2 = Map(1 -> 100, 3 -> 300) 

我想合并它们,并且总结相同键的值。 所以结果是:

 Map(2->20, 1->109, 3->300) 

现在我有2个解决scheme:

 val list = map1.toList ++ map2.toList val merged = list.groupBy ( _._1) .map { case (k,v) => k -> v.map(_._2).sum } 

 val merged = (map1 /: map2) { case (map, (k,v)) => map + ( k -> (v + map.getOrElse(k, 0)) ) } 

但是我想知道是否有更好的解决scheme。

斯卡拉兹有一个半群的概念,捕捉你想在这里做什么,并导致可以说是最短/最干净的解决scheme:

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

具体而言, Map[K, V]的二元运算符结合了映射关键字,将V的半群运算符叠加到任何重复值上。 Int的标准半群使用加法运算符,因此您可以获得每个重复键的值的总和。

编辑 :更多的细节,根据user482745的请求。

在math上, 半群只是一组值,连同一个从该集合中取两个值的运算符,并从该集合中产生另一个值。 因此,在加法下的整数是一个半群,例如 – +运算符将两个整数组合成另一个整型。

你也可以在“具有给定键types和值types的所有映射”的集合上定义一个半群,只要你能想出一些结合两个映射的操作来产生一个新的,这两个映射就是两者的组合投入。

如果两个地图中都没有键,那么这很简单。 如果两个地图中都存在相同的密钥,那么我们需要将这两个密钥对应的值组合起来。 嗯,难道我们刚刚没有描述一个结合了两个相同types实体的操作符吗? 这就是为什么在Scalaz中Map[K, V]的半群存在的充分必要条件是V的半群存在 – V的半群用于组合两个映射到同一个键上的值。

因此,因为Int是这里的值types,所以1键上的“碰撞”通过两个映射值的整数相加来parsing(因为这是Int的半群算子所做的),因此100 + 9 。 如果这些值是string,碰撞会导致两个映射值的string连接(同样,因为这就是String的半群运算符所做的)。

(有趣的是,由于string连接是不可交换的,即"a" + "b" != "b" + "a" – 得到的半群操作也不是,所以map1 |+| map2map2 |+| map1不同string大小写中的map2 |+| map1 ,但不是Int大小写。)

我所知道的最短的答案只使用标准库

 map1 ++ map2.map{ case (k,v) => k -> (v + map1.getOrElse(k,0)) } 

那么,现在在斯卡拉图书馆(至less在2.10)有你想要的东西 – 合并function。 但它只在HashMap中呈现,而不是在Map中。 这有点令人困惑。 此外签名是繁琐的 – 无法想象为什么我需要一个密钥两次,当我需要与另一个关键产生一对。 但是,尽pipe如此,它比以前的“原生”解决scheme更有效。

 val map1 = collection.immutable.HashMap(1 -> 11 , 2 -> 12) val map2 = collection.immutable.HashMap(1 -> 11 , 2 -> 12) map1.merged(map2)({ case ((k,v1),(_,v2)) => (k,v1+v2) }) 

另外在scaladoc中提到过

merged方法平均比执行遍历和从头重新构build一个新的不可变的哈希映射,或++更高性能。

快速解决:

 (map1.keySet ++ map2.keySet).map {i=> (i,map1.getOrElse(i,0) + map2.getOrElse(i,0))}.toMap 

这可以用Simple Scala来实现。 这是一个示例实现。 用这种方法,我们不仅可以合并2个,而且还可以合并一个地图列表。

 // Monoid trait trait Monoid[M] { def zero: M def op(a: M, b: M): M } 

合并两个地图的Monoid特征的基于Map的实现。

 val mapMonoid = new Monoid[Map[Int, Int]] { override def zero: Map[Int, Int] = Map() override def op(a: Map[Int, Int], b: Map[Int, Int]): Map[Int, Int] = (a.keySet ++ b.keySet) map { k => (k, a.getOrElse(k, 0) + b.getOrElse(k, 0)) } toMap } 

现在,如果你有一个需要合并的地图列表(在这种情况下,只有2个),可以像下面这样完成。

 val map1 = Map(1 -> 9 , 2 -> 20) val map2 = Map(1 -> 100, 3 -> 300) val maps = List(map1, map2) // The list can have more maps. val merged = maps.foldLeft(mapMonoid.zero)(mapMonoid.op) 

我写了一篇关于这个的博客文章,看看:

http://www.nimrodstech.com/scala-map-merge/

基本上使用斯卡拉斯半组可以很容易地实现这一点

会看起来像这样:

  import scalaz.Scalaz._ map1 |+| map2 
 map1 ++ ( for ( (k,v) <- map2 ) yield ( k -> ( v + map1.getOrElse(k,0) ) ) ) 

Andrzej Doyle的答案包含了半群的一个很好的解释,它允许你使用|+| 运算符join两个映射并对匹配键进行求和。

有很多方法可以将某些东西定义为types类的实例,而不像OP,您可能不想专门对您的键进行求和。 或者,你可能想要做一个工会而不是一个交叉点。 为了这个目的,Scalaz还为Map增加了额外的function:

https://oss.sonatype.org/service/local/repositories/snapshots/archive/org/scalaz/scalaz_2.11/7.3.0-SNAPSHOT/scalaz_2.11-7.3.0-SNAPSHOT-javadoc.jar/!/的index.html#scalaz.std.MapFunctions

你可以做

 import scalaz.Scalaz._ map1 |+| map2 // As per other answers map1.intersectWith(map2)(_ + _) // Do things other than sum the values 

这是我想出来的…

 def mergeMap(m1: Map[Char, Int], m2: Map[Char, Int]): Map[Char, Int] = { var map : Map[Char, Int] = Map[Char, Int]() ++ m1 for(p <- m2) { map = map + (p._1 -> (p._2 + map.getOrElse(p._1,0))) } map } 

你也可以用猫做。

 import cats.implicits._ val map1 = Map(1 -> 9 , 2 -> 20) val map2 = Map(1 -> 100, 3 -> 300) map1 combine map2 // Map(2 -> 20, 1 -> 109, 3 -> 300) 

我有一个小function来完成这项工作,它是在我的小型库中的一些常用的function,这是不是在标准库。 它应该适用于所有types的可变和不可变的映射,而不仅仅是HashMap

这是用法

 scala> import com.daodecode.scalax.collection.extensions._ scala> val merged = Map("1" -> 1, "2" -> 2).mergedWith(Map("1" -> 1, "2" -> 2))(_ + _) merged: scala.collection.immutable.Map[String,Int] = Map(1 -> 2, 2 -> 4) 

https://github.com/jozic/scalax-collection/blob/master/README.md#mergedwith

这是身体

 def mergedWith(another: Map[K, V])(f: (V, V) => V): Repr = if (another.isEmpty) mapLike.asInstanceOf[Repr] else { val mapBuilder = new mutable.MapBuilder[K, V, Repr](mapLike.asInstanceOf[Repr]) another.foreach { case (k, v) => mapLike.get(k) match { case Some(ev) => mapBuilder += k -> f(ev, v) case _ => mapBuilder += k -> v } } mapBuilder.result() } 

https://github.com/jozic/scalax-collection/blob/master/src%2Fmain%2Fscala%2Fcom%2Fdaodecode%2Fscalax%2Fcollection%2Fextensions%2Fpackage.scala#L190