Scala:合并地图

时间:2013-11-18 11:51:16

标签: scala map merge

如何合并如下地图:

Map1 = Map(1 -> Class1(1), 2 -> Class1(2))
Map2 = Map(2 -> Class2(1), 3 -> Class2(2))

合并后。

Merged = Map( 1 -> List(Class1(1)), 2 -> List(Class1(2), Class2(1)), 3 -> Class2(2))

可以是List,Set或任何其他具有size属性的集合。

14 个答案:

答案 0 :(得分:53)

使用标准的lib,您可以按照以下方式执行:

// convert maps to seq, to keep duplicate keys and concat
val merged = Map(1 -> 2).toSeq ++ Map(1 -> 4).toSeq
// merged: Seq[(Int, Int)] = ArrayBuffer((1,2), (1,4))

// group by key
val grouped = merged.groupBy(_._1)
// grouped: scala.collection.immutable.Map[Int,Seq[(Int, Int)]] = Map(1 -> ArrayBuffer((1,2), (1,4)))


// remove key from value set and convert to list
val cleaned = grouped.mapValues(_.map(_._2).toList)
// cleaned: scala.collection.immutable.Map[Int,List[Int]] = Map(1 -> List(2, 4))

答案 1 :(得分:20)

这是我能提出的最简单的实现,

val m1 = Map(1 -> "1", 2 -> "2")
val m2 = Map(2 -> "21", 3 -> "3")

def merge[K, V](m1:Map[K, V], m2:Map[K, V]):Map[K, List[V]] = 
  (m1.keySet ++ m2.keySet) map { i => i -> (m1.get(i).toList ::: m2.get(i).toList) } toMap

merge(m1, m2) // Map(1 -> List(1), 2 -> List(2, 21), 3 -> List(3))

答案 2 :(得分:15)

您可以使用scalaz

import scalaz._, Scalaz._

val m1 = Map('a -> 1, 'b -> 2)
val m2 = Map('b -> 3, 'c -> 4)

m1.mapValues{List(_)} |+| m2.mapValues{List(_)}
// Map('b -> List(2, 3), 'c -> List(4), 'a -> List(1))

您可以使用Set(_)代替List(_)Set作为Map中的值。

有关|+|运营商的详细信息,请参阅scalaz cheat sheet(或learning scalaz)中的半群

Int |+|适用于+,适用于List - ++适用于Map适用于|+|相同键的值。

答案 3 :(得分:10)

使用cats

的一种干净方法
import cats.implicits._

Map(1 -> "Hello").combine(Map(2 -> "Goodbye"))
//Map(2 -> Goodbye, 1 -> Hello)

重要的是要注意两个地图必须属于同一类型(在这种情况下,Map[Int, String])。

冗长的解释:

combine实际上并不是Map的成员。通过导入cats.implicits,您将带入范围内猫的Map内置monoid实例,以及一些启用简洁语法的隐式类。

以上相当于:

Monoid[Map[Int, String]].combine(Map(1 -> "Hello"), Map(2 -> "Goodbye"))

我们使用Monoid "summoner" function在范围内获取Monoid [Map [Int,String]]实例并使用其组合函数。

答案 4 :(得分:8)

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

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

基本上使用scalaz semi group你可以轻松实现这个目标

看起来像是:

  import scalaz.Scalaz._
  Map1 |+| Map2

答案 5 :(得分:2)

您可以使用foldLeft合并两个相同类型的地图

def merge[A, B](a: Map[A, B], b: Map[A, B])(mergef: (B, Option[B]) => B): Map[A, B] = {
  val (big, small) = if (a.size > b.size) (a, b) else (b, a)
  small.foldLeft(big) { case (z, (k, v)) => z + (k -> mergef(v, z.get(k))) }
}

def mergeIntSum[A](a: Map[A, Int], b: Map[A, Int]): Map[A, Int] =
  merge(a, b)((v1, v2) => v2.map(_ + v1).getOrElse(v1))

示例:

val a = Map("a" -> 1, "b" -> 5, "c" -> 6)
val b = Map("a" -> 4, "z" -> 8)
mergeIntSum(a, b)
res0: Map[String,Int] = Map(a -> 5, b -> 5, c -> 6, z -> 8)

答案 6 :(得分:1)

如果您不想使用原始地图,可以执行以下操作

val target = map1.clone()
val source = map2.clone()
source.foreach(e => target += e._1 -> e._2)

答案 7 :(得分:1)

left.keys map { k => k -> List(left(k),right(k)) } toMap

如果您的两张地图为leftright,则简洁且有效。不确定效率。

但是你的问题有点含糊不清,原因有两个。您没有指定

  1. 值之间的子类型关系(即class1class2),
  2. 如果地图有不同的密钥会怎样?
  3. 对于第一种情况,请考虑以下示例:

    val left = Map("foo" ->1, "bar" ->2)
    val right = Map("bar" -> 'a', "foo" -> 'b')
    

    结果是

    res0: Map[String,List[Int]] = Map(foo -> List(1, 98), bar -> List(2, 97))
    

    请注意Char如何转换为Int,因为scala类型层次结构。更一般地说,如果您的示例class1class2不相关,您将获得List[Any];这可能不是你想要的。

    您可以通过从我的答案中删除List构造函数来解决此问题;这将返回保留类型的Tuple

    res0: Map[String,(Int, Char)] = Map(foo -> (1,b), bar -> (2,a))
    

    第二个问题是当你的地图没有相同的密钥时会发生什么。这将导致key not found异常。换句话说,你是在做两张地图的左,右或内连接吗?您可以通过分别切换到right.keysright.keySet ++ left.keySet来进行右/内连接来消除连接类型的歧义。后者将解决丢失的关键问题,但也许这不是你想要的,也许你想要一个左或右连接。在这种情况下,您可以考虑使用withDefault的{​​{1}}方法来确保每个键都返回一个值,例如Map,但这需要更多的工作。

答案 8 :(得分:1)

.m

如jwvh所述,如果Class1不是Class2的上限类型,则应明确指定List类型。 CommonType是Class1和Class2的上限类型。

答案 9 :(得分:1)

Scala 2.13开始,另一种仅基于标准库的解决方案包括使用groupMap,该方法(顾名思义)与groupBy后跟mapValues等效:

// val m1 = Map(1 -> "a", 2 -> "b")
// val m2 = Map(2 -> "c", 3 -> "d")
(m1.toSeq ++ m2).groupMap(_._1)(_._2)
// Map[Int,Seq[String]] = Map(2 -> List("b", "c"), 1 -> List("a"), 3 -> List("d"))

此:

  • 将两个映射串联为一个元组(List((1,"a"), (2,"b"), (2,"c"), (3,"d")))序列。为了简洁起见,m2被隐式地转换为Seq,以适应m1.toSeq的类型-但您可以选择使用{{1}使其明确}。

  • m2.toSeq的元素基于它们的第一个元组部分(group)(地图的组部分)

  • _._1的值分为第二个元组部分(map)(组 Map 的映射部分)

答案 10 :(得分:1)

通过Scala Cats(由@David Castillo提供的略有改进的版本)组合两个地图:Map[A,B],结果类型:Map[A,List[B]]的解决方案

///将每个原始地图转换为Map [A​​,List [B]]。 //将Monoid [List]的实例添加到合并列表的范围内:

import cats.instances.map._ // for Monoid
import cats.syntax.semigroup._ // for |+|
import cats.instances.list._

val map1 = Map("a" -> 1, "b" -> 2)
  .mapValues(List(_))
val map2 = Map("b" -> 3, "d" -> 4)
  .mapValues(List(_))
map1 |+| map2

答案 11 :(得分:0)

有一个名为scala-collection-contrib的Scala模块,它提供了非常有用的方法,例如mergeByKey

首先,我们需要向build.sbt添加一个附加依赖项:

libraryDependencies += "org.scala-lang.modules" %% "scala-collection-contrib" % "0.1.0"

然后可以像这样合并:

import scala.collection.decorators._

val map1 = Map(1 -> Class1(1), 2 -> Class1(2))
val map2 = Map(2 -> Class2(1), 3 -> Class2(2))

map1.mergeByKeyWith(map2){
  case (a,b) => a.toList ++ b.toList
}

答案 12 :(得分:0)

这是两个地图合并

  def mergeMap[A, B](map1: Map[A, B], map2: Map[A, B], op: (B, B) => B, default: => B): Map[A, B] = (map1.keySet ++ map2.keySet).map(x => (x, op(map1.getOrElse(x, default), map2.getOrElse(x, default)))).toMap

这是多张地图合并

  def mergeMaps[A, B](maps: Seq[Map[A, B]], op: (B, B) => B, default: => B): Map[A, B] = maps.reduce((a, b) => mergeMap(a, b, op, default))

答案 13 :(得分:0)

此答案虽然解决了一个通过通用键合并两个地图的常见/相关场景,但并不能直接解决最初的问题。

基于@Drexin的回答,我编写了一种通用方法,通过为Maps提供join方法来扩展现有的Map功能:

object implicits {
  type A = Any
  implicit class MapExt[K, B <: A, C <: A](val left: immutable.Map[K, B]) {
    def join(right: immutable.Map[K, C]) : immutable.Map[K, Seq[A]] = {
      val inter = left.keySet.intersect(right.keySet)

      val leftFiltered =  left.filterKeys{inter.contains}
      val rightFiltered = right.filterKeys{inter.contains}

      (leftFiltered.toSeq ++ rightFiltered.toSeq)
        .groupBy(_._1)
        .mapValues(_.map{_._2}.toList)
    }
  }
}

注释:

  • 联接基于键的交集,类似于SQL世界中的“内部联接”。
  • 它可以与Scala <= 2.12一起使用,因为Scala 2.13可以按照@Xavier Guihot的建议使用groupMap
  • 考虑将type A替换为您自己的基本类型。

用法:

import implicits._

val m1 = Map("k11" -> "v11", "k12" -> "v12")
val m2 = Map("k11" -> "v21", "k12" -> "v22", "k13" -> "v23")

println (m1 join m2)
// Map(k11 -> List(v11, v21), k12 -> List(v12, v22))