最好的方法来合并两个地图和总结相同的密钥的值?
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 |+| map2
与map2 |+| 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:
你可以做
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() }