我有一个Map [String,Double]列表,我想将它们的内容合并到一个Map [String,Double]中。我该如何以惯用的方式做到这一点?我想我应该可以用折叠来做到这一点。类似的东西:
val newMap = Map[String, Double]() /: listOfMaps { (accumulator, m) => ... }
此外,我想以通用的方式处理关键冲突。也就是说,如果我向已经存在的地图添加一个键,我应该能够指定一个返回Double的函数(在这种情况下)并获取该键的现有值,加上我想要添加的值。如果地图中尚不存在该关键字,则只需添加该关键字及其值即可。
在我的特定情况下,我想构建一个Map [String,Double],这样如果地图已经包含一个键,那么Double将被添加到现有的地图值中。
我正在使用特定代码中的可变映射,但如果可能的话,我对更通用的解决方案感兴趣。
答案 0 :(得分:40)
嗯,你可以这样做:
mapList reduce (_ ++ _)
除了特殊的碰撞要求外。
既然你确实有这个特殊要求,也许最好的就是做这样的事情(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
}
然后,您可以通过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 _)
虽然这是用2.8写的,但keysIterator
对于2.7来说变为keys
,filterKeys
可能需要用filter
和map
来写, &
变为**
,依此类推,它应该不会太大。
答案 1 :(得分: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)
它适用于2.7.5和2.8.0。
答案 2 :(得分:21)
我很惊讶没有人提出这个解决方案:
myListOfMaps.flatten.toMap
完全符合您的要求:
示例:
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)
flatten
将地图列表转换为元组的平面列表,toMap
将元组列表转换为删除了所有重复键的地图
答案 3 :(得分:3)
我很快就读到了这个问题所以我不确定我是否遗漏了某些东西(比如它必须适用于2.7.x或没有scalaz):
import scalaz._
import Scalaz._
val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
ms.reduceLeft(_ |+| _)
// returns Map(goodbye -> 3.3, hello -> 5.5, world -> 2.2)
您可以更改Double的monoid定义并获得另一种累积值的方法,这里得到最大值:
implicit val dbsg: Semigroup[Double] = semigroup((a,b) => math.max(a,b))
ms.reduceLeft(_ |+| _)
// returns Map(goodbye -> 3.3, hello -> 4.4, world -> 2.2)
答案 4 :(得分:2)
有趣的是,我对此有所了解,我得到了以下内容(在2.7.5上):
一般地图:
def mergeMaps[A,B](collisionFunc: (B,B) => B)(listOfMaps: Seq[scala.collection.Map[A,B]]): Map[A, B] = {
listOfMaps.foldLeft(Map[A, B]()) { (m, s) =>
Map(
s.projection.map { pair =>
if (m contains pair._1)
(pair._1, collisionFunc(m(pair._1), pair._2))
else
pair
}.force.toList:_*)
}
}
但是男人,这对于投射和强迫以及toList和诸如此类的东西来说是可怕的。另外一个问题:什么是更好的方式来解决这个问题?
对于可变映射,这是我在我的代码中处理的,并且使用不太通用的解决方案,我得到了这个:
def mergeMaps[A,B](collisionFunc: (B,B) => B)(listOfMaps: List[mutable.Map[A,B]]): mutable.Map[A, B] = {
listOfMaps.foldLeft(mutable.Map[A,B]()) {
(m, s) =>
for (k <- s.keys) {
if (m contains k)
m(k) = collisionFunc(m(k), s(k))
else
m(k) = s(k)
}
m
}
}
这似乎有点干净,但只会在编写时使用可变映射。有趣的是,我首先使用/:而不是foldLeft尝试了上述(在我提出问题之前),但是我遇到了类型错误。我认为/:和foldLeft基本相同,但编译器一直在抱怨我需要(m,s)的显式类型。怎么了?
答案 5 :(得分:2)
我写了一篇关于此的博文,请查看:
http://www.nimrodstech.com/scala-map-merge/
基本上使用scalaz semi group你可以轻松实现这个目标
看起来像是:
import scalaz.Scalaz._
listOfMaps reduce(_ |+| _)
答案 6 :(得分:0)
oneliner helper-func,其用法几乎与使用scalaz一样干净:
def mergeMaps[K,V](m1: Map[K,V], m2: Map[K,V])(f: (V,V) => V): Map[K,V] =
(m1 -- m2.keySet) ++ (m2 -- m1.keySet) ++ (for (k <- m1.keySet & m2.keySet) yield { k -> f(m1(k), m2(k)) })
val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
ms.reduceLeft(mergeMaps(_,_)(_ + _))
// returns Map(goodbye -> 3.3, hello -> 5.5, world -> 2.2)
最终可读性将其包装在隐式自定义类型中:
class MyMap[K,V](m1: Map[K,V]) {
def merge(m2: Map[K,V])(f: (V,V) => V) =
(m1 -- m2.keySet) ++ (m2 -- m1.keySet) ++ (for (k <- m1.keySet & m2.keySet) yield { k -> f(m1(k), m2(k)) })
}
implicit def toMyMap[K,V](m: Map[K,V]) = new MyMap(m)
val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
ms reduceLeft { _.merge(_)(_ + _) }
答案 7 :(得分:0)
从Scala 2.13
开始,这是另一个处理重复键并且仅基于标准库的解决方案,其中包括将Map
作为序列合并(flatten
)之前应用新的groupMapReduce运算符,顾名思义,该运算符等效于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)
此:
flatten
s(将地图映射为一系列元组(List(("hello", 1.1), ("world", 2.2), ("goodbye", 3.3), ("hello", 4.4))
),保留所有键/值(甚至重复的键)
group
的元素基于它们的第一个元组部分(_._1
)(组 MapReduce的组部分)
map
的值分为第二个元组部分(_._2
)(组 Map Reduce的映射部分)
reduce
的映射分组值(_+_
)通过求和(但可以是任何reduce: (T, T) => T
函数)(减少groupMap Reduce )
groupMapReduce
步骤可以看作是one-pass version的等效项:
list.groupBy(_._1).mapValues(_.map(_._2).reduce(_ + _))