38

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

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

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

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

I'm working with mutable maps in my specific code, but I'm interested in more generic solutions, if possible.

4

8 回答 8

48

Well, you could do:

mapList reduce (_ ++ _)

except for the special requirement for collision.

Since you do have that special requirement, perhaps the best would be doing something like this (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
}

You can then add this method to the map class through the Pimp My Library pattern, and use it in the original example instead of "++":

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

While this was written in 2.8, so keysIterator becomes keys for 2.7, filterKeys might need to be written in terms of filter and map, & becomes **, and so on, it shouldn't be too different.

于 2009-08-11T21:27:09.757 回答
28

这个怎么样:

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。

于 2009-08-12T07:40:03.697 回答
26

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

myListOfMaps.flatten.toMap

完全满足您的需要:

  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)

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

于 2014-08-20T07:06:15.053 回答
5

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

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)

这:

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

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

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

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


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

list.groupBy(_._1).mapValues(_.map(_._2).reduce(_ + _))
于 2019-02-10T12:54:59.107 回答
2

Interesting, noodling around with this a bit, I got the following (on 2.7.5):

General Maps:

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

这似乎更简洁一些,但仅适用于编写的可变 Maps。有趣的是,我首先尝试使用/:而不是 foldLeft 进行上述操作(在我提出问题之前),但我遇到了类型错误。我认为 /: 和 foldLeft 基本上是等价的,但是编译器一直抱怨我需要 (m, s) 的显式类型。那是怎么回事?

于 2009-08-11T22:45:58.973 回答
2

我很快阅读了这个问题,所以我不确定我是否遗漏了一些东西(比如它必须适用于 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)
于 2012-09-11T23:55:18.143 回答
2

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

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

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

看起来像:

  import scalaz.Scalaz._
  listOfMaps reduce(_ |+| _)
于 2014-07-29T13:44:23.933 回答
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(_)(_ + _) } 
于 2014-06-15T20:46:48.080 回答