比例:工作地图

问题描述 投票:29回答:10

如何合并如下地图:

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属性的任何其他集合。

scala map merge
10个回答
57
投票

使用标准库,您可以按如下方式执行:

// 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
投票
m2.foldLeft(m1.mapValues{List[CommonType](_)}) { case (acc, (k, v)) =>
  acc.updated(k, acc.getOrElse(k, List.empty) :+ v)
}

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


1
投票

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

//将每个原始地图转换为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

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
}

21
投票

这是我能想到的最简单的实现,

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

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(_)Sets作为Map中的值。

有关scalaz cheat sheet算子的详细信息,请参阅learning scalaz(或|+|)中的Semigroup。

对于Int |+|作为+List - 作为++,对于Map,它将|+|应用于相同键的值。


10
投票

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

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

基本上使用scalaz semi group你可以轻松实现这一点

看起来像是这样的:

  import scalaz.Scalaz._
  Map1 |+| Map2

10
投票

cats做一个干净的方法:

import cats.implicits._

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

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

很长的解释:

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

以上相当于:

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

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


3
投票

您可以使用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)

3
投票

启动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的类型 - 但你可以选择通过使用m2.toSeq使其明确。
  • groups元素基于它们的第一个元组部分(_._1)(groupMap的组部分)
  • maps将值分组到第二个元组部分(_._2)(mapMap的一部分)

1
投票

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

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

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

如果您的两张地图是leftright,那么简洁并且会有效。效率不确定。

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

  1. 值之间的子类型关系(即class1class2),
  2. 如果地图有不同的密钥会发生什么

对于第一种情况,请考虑以下示例:

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

请注意Chars如何转换为Ints,因为scala类型层次结构。更一般地说,如果在你的例子中class1class2不相关,你会得到一个List[Any];这可能不是你想要的。

你可以通过从我的答案中删除List构造函数来解决这个问题;这将返回保存类型的Tuples:

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

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

© www.soinside.com 2019 - 2024. All rights reserved.