合并两个地图并将相同键值相加的最佳方法是什么?

Fre*_*ind 169 merge scala map

val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
Run Code Online (Sandbox Code Playgroud)

我想合并它们,并将相同键的值相加.结果将是:

Map(2->20, 1->109, 3->300)
Run Code Online (Sandbox Code Playgroud)

现在我有2个解决方案:

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

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

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

Rex*_*err 146

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

map1 ++ map2.map{ case (k,v) => k -> (v + map1.getOrElse(k,0)) }
Run Code Online (Sandbox Code Playgroud)

  • 好的解决方案 我想添加一个提示,即`++`从右边的地图中替换(++)(这里是map1)左侧地图中的任何(k,v)乘以(k,v),如果(k) ,_)已存在于左侧地图(此处为map1),例如`Map(1-> 1)++ Map(1-> 2)导致Map(1-> 2) (33认同)

And*_*yle 141

Scalaz有一个概念半群捕获你想在这里做什么,并导致无疑是最短/干净的解决方案:

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)
Run Code Online (Sandbox Code Playgroud)

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

编辑:根据user482745的要求更多细节.

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

您还可以在"具有给定键类型和值类型的所有地图"的集合上定义半群,只要您能够提出一些组合两个地图以生成新地图的操作,这两个地图就是两者的组合.投入.

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

因此,因为Int这里是值类型,所以1键上的"碰撞" 通过两个映射值的整数相加来解析(因为这是Int的半群运算符所做的),因此100 + 9.如果值是字符串,则碰撞会导致两个映射值的字符串连接(同样,因为这是String的半群运算符所做的).

(有趣的是,因为字符串连接不是可交换的 - 也就是说,"a" + "b" != "b" + "a"所得到的半群操作也不是.所以map1 |+| map2不同于map2 |+| map1String情况,但不是在Int情况下.)

  • 辉煌!第一个实际例子,其中`scalaz`是有意义的. (35认同)
  • 开玩笑吧!如果你开始寻找它...它到处都是.引用规范和规格的erric torrebone作者2:"首先你学习选择,然后你开始在任何地方看到它.然后你学习Applicative并且它是一样的.接下来?" 接下来是更多功能概念.这些可以帮助您构建代码并很好地解决问题. (5认同)
  • 实际上,当我终于找到Scala时,我一直在寻找五年的Option.*可能*为null的Java对象引用与不可能的Java对象引用之间的差异(即在`A`和`Option [A]`之间)是如此巨大,我无法相信它们实际上是同一类型.我*刚开始看Scalaz.我不确定我是否足够聪明...... (4认同)

Mat*_*ell 47

快速解决方案

(map1.keySet ++ map2.keySet).map {i=> (i,map1.getOrElse(i,0) + map2.getOrElse(i,0))}.toMap
Run Code Online (Sandbox Code Playgroud)


Mik*_*sov 39

好吧,现在在scala库中(至少在2.10中)有你想要的东西 - 合并功能.但它仅在HashMap中呈现,而不在Map中.这有点令人困惑.签名也很麻烦 - 无法想象为什么我需要两次密钥以及当我需要用另一把密钥生成一对时.但是,它比以前的"原生"解决方案更有效,更清洁.

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) })
Run Code Online (Sandbox Code Playgroud)

同样在scaladoc中提到过

merged方法平均比执行遍历和从头开始重建新的不可变哈希映射更高效,或者++.

  • 这非常令人讨厌,他们只对HashMaps说实话. (2认同)
  • 似乎在2.11版本中发生了变化.查看2.10 scaladoc - http://www.scala-lang.org/api/2.10.1/index.html#scala.collection.immutable.HashMap有一个常用的功能.但是在2.11中它是`MergeFunction`. (2认同)

Jeg*_*gan 13

这可以用简单的Scala 实现为Monoid.这是一个示例实现.通过这种方法,我们不仅可以合并2,还可以合并地图列表.

// Monoid trait

trait Monoid[M] {
  def zero: M
  def op(a: M, b: M): M
}
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

现在,如果您有一个需要合并的地图列表(在这种情况下,只有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)
Run Code Online (Sandbox Code Playgroud)


Ami*_*ico 5

map1 ++ ( for ( (k,v) <- map2 ) yield ( k -> ( v + map1.getOrElse(k,0) ) ) )
Run Code Online (Sandbox Code Playgroud)


Nim*_*007 5

我写了一篇关于此的博客文章,请查看:

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

基本上使用scalaz semi group你可以很容易地实现这一点

看起来像是这样的:

  import scalaz.Scalaz._
  map1 |+| map2
Run Code Online (Sandbox Code Playgroud)

  • 您需要在答案中添加更多细节,最好是一些实现代码.这也适用于您发布的其他类似答案,并针对所询问的具体问题定制每个答案.**经验法则:**如果不点击博客链接,提问者应该能够从您的答案中受益. (11认同)

Art*_*hou 5

您也可以使用Cats做到这一点。

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)
Run Code Online (Sandbox Code Playgroud)


Xav*_*hot 5

从 开始Scala 2.13,另一个仅基于标准库的解决方案包括替换groupBy解决方案的一部分,其中groupMapReduce(顾名思义)相当于groupBy后跟mapValues和减少步骤:

// val map1 = Map(1 -> 9, 2 -> 20)
// val map2 = Map(1 -> 100, 3 -> 300)
(map1.toSeq ++ map2).groupMapReduce(_._1)(_._2)(_+_)
// Map[Int,Int] = Map(2 -> 20, 1 -> 109, 3 -> 300)
Run Code Online (Sandbox Code Playgroud)

这:

  • 将两个映射连接为元组序列 ( List((1,9), (2,20), (1,100), (3,300)))。为了简洁起见,map2隐式转换为Seq以适应 - 的类型map1.toSeq,但您可以选择使用 使其显式map2.toSeq

  • groups 元素基于其第一个元组部分( MapReduce的组部分),

  • map将值分组到其第二元组部分(MapReduce组的映射部分),

  • reduce_+_通过求和来映射映射值 ( )(减少 groupMap Reduce的一部分)。