Scala:如何合并Map集合

dluptydi  于 2023-10-18  发布在  Scala
关注(0)|答案(9)|浏览(155)

我有一个Map[String,Double]的列表,我想将它们的内容合并到一个Map[String,Double]中。我该如何用一种习惯的方式来做这件事呢?我想我应该可以用折叠来做这件事。例如:

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

此外,我想以一种通用的方式处理密钥冲突。也就是说,如果我向Map添加一个已经存在的键,我应该能够指定一个返回Double(在本例中)的函数,并接受该键的现有值,加上我试图添加的值。如果键还不存在于Map中,那么只需添加它,并且其值不变。
在我的特定情况下,我想构建一个Map[String,Double],这样如果map已经包含一个键,那么Double将被添加到现有的map值中。
我在我的特定代码中使用可变Map,但如果可能的话,我对更通用的解决方案感兴趣。

0s0u357o

0s0u357o1#

你可以这样做:

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模式将此方法添加到map类,并在原始示例中使用它而不是“++“:

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

虽然这是在Python 2.8中写的,所以对于Python 2.7,keysIterator变成了keysfilterKeys可能需要用filtermap来写,&变成了**,等等,它不应该有太大的不同。

ygya80vv

ygya80vv2#

这个怎么样:

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中都可以工作。

czq61nw1

czq61nw13#

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

myListOfMaps.flatten.toMap

完全满足您的需求:
1.将列表合并为单个Map
1.清除任何重复的密钥
范例:

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将Map列表转换为元组的平面列表,toMap将元组列表转换为删除了所有重复键的Map

jucafojl

jucafojl4#

Scala 2.13开始,另一个解决方案是处理重复键,并且只基于标准库,包括在应用新的groupMapReduce操作符之前将Map s合并为序列(flatten),该操作符(顾名思义)相当于groupBy,然后是Map和分组值的减少步骤:

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(连接)Map为元组序列(List(("hello", 1.1), ("world", 2.2), ("goodbye", 3.3), ("hello", 4.4))),保留所有键/值(甚至是重复键)
  • group s个元素基于它们的第一个元组部分(_._1)(groupMapReduce的group部分)
  • map s分组的值到它们的第二个元组部分(_._2)(map part of groupMapReduce)
  • reduce s通过求和来Map分组值(_+_)(但它可以是任何reduce: (T, T) => T函数)(reduce groupMap的一部分Reduce

groupMapReduce步骤可以看作是相当于以下步骤的一次通过版本:

list.groupBy(_._1).mapValues(_.map(_._2).reduce(_ + _))
nkoocmlb

nkoocmlb5#

有趣的是,我在这方面做了一点,我得到了以下内容(在2.7.5):
一般Map:

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和诸如此类的。单独提问:有什么更好的方法来解决这个问题呢
对于可变Map,这是我在代码中处理的,并且使用不太通用的解决方案,我得到了这个:

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

这看起来更干净一点,但只适用于编写的可变Map。有趣的是,我第一次尝试上面(在我问问题之前)使用/:而不是foldLeft,但我得到了类型错误。我想/:和foldLeft基本上是等价的,但是编译器一直抱怨我需要(m,s)的显式类型。这是怎么回事

nfzehxib

nfzehxib6#

我阅读这个问题很快,所以我不确定我是否错过了什么(比如它必须适用于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定义,并获得另一种方法来累积值,这里获得max:

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

eufgjt7s7#

我写了一篇关于这个的博客,看看吧:
http://www.nimrodstech.com/scala-map-merge/
基本上使用scalaz半组你可以很容易地实现这一点
看起来像这样:

import scalaz.Scalaz._
  listOfMaps reduce(_ |+| _)
kokeuurv

kokeuurv8#

一个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)

为了最终的可读性,将其 Package 在隐式自定义类型中:

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(_)(_ + _) }
iswrvxsc

iswrvxsc9#

def mergeMap[A, B](ms: List[Map[A, B]])(f: (B, B) => B): Map[A, B] = {
  ms.flatten.foldLeft(Map[A, B]()) { case (acc, (k, v)) =>
    acc + (if (acc.contains(k)) k -> f(acc(k), v) else (k, v))
  }
}

相关问题