Scala:如何合并地图集合

Jef*_*eff 34 scala map folding scala-collections

我有一个Map [String,Double]列表,我想将它们的内容合并到一个Map [String,Double]中.我该如何以惯用的方式做到这一点?我想我应该可以用折叠来做到这一点.就像是:

val newMap = Map[String, Double]() /: listOfMaps { (accumulator, m) => ... }
Run Code Online (Sandbox Code Playgroud)

此外,我想以通用的方式处理关键冲突.也就是说,如果我向已经存在的地图添加一个键,我应该能够指定一个返回Double的函数(在这种情况下)并获取该键的现有值,加上我试图添加的值.如果地图中尚不存在该关键字,则只需添加该关键字并使其值不变.

在我的具体情况下,我想构建一个Map [String,Double],这样如果地图已经包含一个键,那么Double将被添加到现有的地图值中.

我在我的特定代码中使用可变映射,但如果可能的话,我对更通用的解决方案感兴趣.

Dan*_*ral 43

好吧,你可以这样做:

mapList reduce (_ ++ _)
Run Code Online (Sandbox Code Playgroud)

除了特殊的碰撞要求.

既然你确实有这个特殊的要求,也许最好的做这样的事情(2.8):

def combine(m1: Map, m2: Map): Map = {
  val k1 = Set(m1.keysIterator.toList: _*)
  val k2 = Set(m2.keysIterator.toList: _*)
  val intersection = k1 & k2

  val r1 = for(key <- intersection) yield (key -> (m1(key) + m2(key)))
  val r2 = m1.filterKeys(!intersection.contains(_)) ++ m2.filterKeys(!intersection.contains(_)) 
  r2 ++ r1
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以通过Pimp My Library模式将此方法添加到地图类,并在原始示例中使用它而不是" ++":

class CombiningMap(m1: Map[Symbol, Double]) {
  def combine(m2: Map[Symbol, Double]) = {
    val k1 = Set(m1.keysIterator.toList: _*)
    val k2 = Set(m2.keysIterator.toList: _*)
    val intersection = k1 & k2
    val r1 = for(key <- intersection) yield (key -> (m1(key) + m2(key)))
    val r2 = m1.filterKeys(!intersection.contains(_)) ++ m2.filterKeys(!intersection.contains(_))
    r2 ++ r1
  }
}

// Then use this:
implicit def toCombining(m: Map[Symbol, Double]) = new CombiningMap(m)

// And finish with:
mapList reduce (_ combine _)
Run Code Online (Sandbox Code Playgroud)

虽然这是写在2.8,所以keysIterator变成keys了2.7,filterKeys可能需要在以下方面进行书面filtermap,&**,等等,它应该不会太不同.

  • 有点无视忽略该要求的观点. (2认同)

Wal*_*ang 27

这个怎么样:

def mergeMap[A, B](ms: List[Map[A, B]])(f: (B, B) => B): Map[A, B] =
  (Map[A, B]() /: (for (m <- ms; kv <- m) yield kv)) { (a, kv) =>
    a + (if (a.contains(kv._1)) kv._1 -> f(a(kv._1), kv._2) else kv)
  }

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
val mm = mergeMap(ms)((v1, v2) => v1 + v2)

println(mm) // prints Map(hello -> 5.5, world -> 2.2, goodbye -> 3.3)
Run Code Online (Sandbox Code Playgroud)

它适用于2.7.5和2.8.0.


Ele*_*fee 22

我很惊讶没有人提出这个解决方案:

myListOfMaps.flatten.toMap
Run Code Online (Sandbox Code Playgroud)

完全符合您的要求:

  1. 将列表合并到单个Map
  2. 清掉任何重复的密钥

例:

scala> List(Map('a -> 1), Map('b -> 2), Map('c -> 3), Map('a -> 4, 'b -> 5)).flatten.toMap
res7: scala.collection.immutable.Map[Symbol,Int] = Map('a -> 4, 'b -> 5, 'c -> 3)
Run Code Online (Sandbox Code Playgroud)

flatten将地图列表转换为元组的平面列表,将元组toMap列表转换为地图,删除所有重复键

  • 这正是我所需要的,但不像OP所要求的那样对重复键的值求和. (2认同)

Xav*_*hot 5

开始Scala 2.13,另一种处理重复键且仅基于标准库的解决方案包括在应用新的groupMapReduce运算符之前将Maps合并为序列 ( flatten),该运算符(顾名思义)相当于 a后跟一个映射和一个化简分组值的步骤:groupBy

List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
  .flatten
  .groupMapReduce(_._1)(_._2)(_ + _)
// Map("world" -> 2.2, "goodbye" -> 3.3, "hello" -> 5.5)
Run Code Online (Sandbox Code Playgroud)

这个:

  • flattens(连接)映射为元组序列 ( List(("hello", 1.1), ("world", 2.2), ("goodbye", 3.3), ("hello", 4.4))),保留所有键/值(甚至重复键)

  • groups 个元素基于它们的第一个元组部分 ( _._1) (MapReduce 的组部分)

  • maps 将值分组到它们的第二个元组部分 ( _._2) (组Map Reduce 的映射部分)

  • reduces_+_通过取它们的总和来映射分组值 ( )(但它可以是任何reduce: (T, T) => T函数)(减少 groupMap Reduce 的一部分)


groupMapReduce步骤可以看作是一个一次性版本,相当于:

list.groupBy(_._1).mapValues(_.map(_._2).reduce(_ + _))
Run Code Online (Sandbox Code Playgroud)