如何将两个地图合并并对相同键的值求和?
如何将两个地图合并并对相同键的值求和?
val map1 = Map(1 -> 9 , 2 -> 20) val map2 = Map(1 -> 100, 3 -> 300)
我想合并它们,并将相同键的值相加。所以结果将是:
Map(2->20, 1->109, 3->300)
现在我有两种解决方案:
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)) ) }
但我想知道是否有更好的解决方案。
Scalaz有一个叫做Semigroup的概念,它捕捉了您想在此处执行的操作,并导致了可能是最短/最清晰的解决方案:
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
的标准Semigroup使用加法运算符,因此您会获取每个重复键的值之和。
编辑:根据用户482745的要求,提供更多细节。
从数学上讲,semigroup只是一个值集合,以及从该集合中选择两个值并生成该集合中的另一个值的运算符。例如,加法下的整数是一个半群-+
运算符将两个整数组合在一起以生成另一个整数。
您还可以定义集合的“所有映射与给定键类型和值类型”,只要您可以想出某种操作,该操作将两个映射组合起来生成一种某种方式结合了两个输入的新映射。
如果没有键同时出现在两个映射中,则这很简单。如果同一键存在于两个映射中,则需要组合键映射到的两个值。嗯,我们刚刚描述了一种组合两个相同类型实体的运算符吗?这就是为什么在Scalaz中仅当V
的Semigroup存在时Map[K,V]
的semigroup存在-V
的semigroup用于组合分配给相同键的两个映射的值。
因为这里的Int
是值类型,所以1
键上的“冲突”通过两个映射值的整数加法解决(因为这是Int的semigroup运算符所做的),因此是100 + 9
。如果值是字符串,冲突会导致两个映射值的字符串连接(同样是因为这是字符串的semigroup运算符所做的)。
(有趣的是,因为字符串连接不是可交换的 - 也就是说,"a" + "b"!= "b" + "a"
- 结果的semigroup操作也不是可交换的。所以在字符串情况下,map1 |+| map2
与map2 |+| map1
是不同的,但不是在Int情况下。)