小编典典

合并两个地图并对相同键的值求和的最佳方法?

all

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)) )
}

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


阅读 72

收藏
2022-07-31

共1个答案

小编典典

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)

具体来说,二元运算符 forMap[K, V]组合了映射的键,将V的半群运算符折叠在任何重复值上。标准半群Int使用加法运算符,因此您可以获得每个重复键的值的总和。

编辑 :根据 user482745 的要求,提供更多细节。

从数学上讲,半群只是一组值,以及一个从该集合中获取两个值并从该集合中产生另一个值的运算符。因此,加法下的整数是一个半群,例如
-+运算符将两个整数组合成另一个整数。

您还可以在“具有给定键类型和值类型的所有映射”的集合上定义一个半组,只要您能想出一些操作来组合两个映射以产生一个新的映射,这在某种程度上是两者的组合输入。

如果两个地图中都没有出现键,这是微不足道的。如果两个映射中存在相同的键,那么我们需要组合键映射到的两个值。嗯,我们不是刚刚描述了一个结合两个相同类型实体的运算符吗?这就是为什么在
Scalaz 中,Map[K, V]当且仅当 Semigroup for 存在时,半群才V存在
-V的半群用于组合来自分配给同一键的两个映射的值。

所以因为Int这里是值类型,所以1键上的“冲突”是通过两个映射值的整数相加来解决的(这就是 Int 的半群运算符所做的),因此100 + 9.
如果值是字符串,则冲突将导致两个映射值的字符串连接(同样,因为这是字符串的半群运算符所做的)。

(有趣的是,因为字符串连接不是可交换的——也就是说,"a" + "b" != "b" + "a"生成的半群运算也不是。所以与 String
情况map1 |+| map2不同map2 |+| map1,但在 Int 情况下不同。)

2022-07-31