Scala:合并地图

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20047080/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 05:52:04  来源:igfitidea点击:

Scala: Merge map

scalamapmerge

提问by Robinho

How can I merge maps like below:

如何合并如下地图:

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

After merged.

合并后。

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

Can be List, Set or any other collection who has size attribute.

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

回答by drexin

Using the standard lib, you can do it as follows:

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

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

回答by tiran

This is the simplest implementation i could come up with,

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

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

回答by senia

You could use scalaz:

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

You could use Set(_)instead of List(_)to get Sets as values in Map.

您可以使用Set(_)而不是List(_)Sets 作为Map.

See Semigroupin scalaz cheat sheet(or in learning scalaz) for details about |+|operator.

半群scalaz小抄(或学习scalaz)有关详细信息|+|运营商。

For Int|+|works as +, for List- as ++, for Mapit applies |+|to values of same keys.

ForInt|+|用作+, for List-as ++,因为Map它适用|+|于相同键的值。

回答by David Castillo

One clean way to do it, with cats:

一种干净的方法,用

import cats.implicits._

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

It's important to note that both maps have to be of the same type (in this case, Map[Int, String]).

重要的是要注意两个地图必须是相同的类型(在本例中为Map[Int, String])。

Long explanation:

长解释:

combineisn't really a member of Map. By importing cats.implicits you're bringing into scope cats's Map built-in monoid instances, along with some implicit classes which enable the terse syntax.

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

The above is equivalent to this:

以上等价于:

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

Where we're using the Monoid "summoner" functionto get the Monoid[Map[Int, String]] instance in scope and using its combine function.

我们使用Monoid“summoner”函数来获取作用域中的 Monoid[Map[Int, String]] 实例并使用它的 combine 函数。

回答by Nimrod007

I wrote a blog post about this , check it out :

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

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

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

basically using scalaz semi group you can achieve this pretty easily

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

would look something like :

看起来像:

  import scalaz.Scalaz._
  Map1 |+| Map2

回答by Xavier Guihot

Starting Scala 2.13, another solution only based on the standard library consists in using groupMapwhich (as its name suggests) is an equivalent of a groupByfollowed by mapValues:

开始Scala 2.13,另一种仅基于标准库的解决方案在于使用groupMapwhich(顾名思义)相当于 agroupBy后跟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"))

This:

这:

  • Concatenates the two maps as a sequence of tuples (List((1,"a"), (2,"b"), (2,"c"), (3,"d"))). For conciseness, m2is implicitlyconverted to Seqto adapt to the type of m1.toSeq- but you could choose to make it explicit by using m2.toSeq.

  • groups elements based on their first tuple part (_._1) (group part of groupMap)

  • maps grouped values to their second tuple part (_._2) (map part of groupMap)

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

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

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

回答by x4444

You can use foldLeft to merge two Maps of the same type

您可以使用 foldLeft 来合并两个相同类型的 Map

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

Example:

例子:

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)

回答by Alx

m2.foldLeft(m1.mapValues{List[CommonType](_)}) { case (acc, (k, v)) =>
  acc.updated(k, acc.getOrElse(k, List.empty) :+ v)
}

As noted by jwvh, List type should be specified explicitly if Class1 is not upper type bound for Class2. CommonType is a type which is upper bound for both Class1 and Class2.

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

回答by smishra

If you don't want to mess around with original maps you could do something like following

如果您不想弄乱原始地图,则可以执行以下操作

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

回答by Luciano

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

Is concise and will work, assuming your two maps are leftand right. Not sure about efficiency.

假设您的两个地图是left和,则简洁且有效right。不清楚效率。

But your question is a bit ambiguous, for two reasons. You don't specify

但是你的问题有点模棱两可,有两个原因。你没有指定

  1. The subtyping relationship between the values (i.e. class1,class2),
  2. What happens if the maps have different keys
  1. 值之间的子类型关系(即class1class2),
  2. 如果地图有不同的键会发生什么

For the first case, consider the following example:

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

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

Which results in

这导致

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

Notice how the Chars have been converted to Ints, because of the scala type hierarchy. More generally, if in your example class1and class2are not related, you would get back a List[Any]; this is probably not what you wanted.

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

You can work around this by dropping the Listconstructor from my answer; this will return Tuples which preserve the type:

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

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

The second problem is what happens when you have maps that don't have the same keys. This will result in a key not foundexception. Put in another way, are you doing a left, right, or inner join of the two maps? You can disambiguate the type of join by switching to right.keysor right.keySet ++ left.keySetfor right/inner joins respectively. The later will work around the missing key problem, but maybe that's not what you want i.e. maybe you want a left or right join instead. In that case you can consider using the withDefaultmethod of Mapto ensure every key returns a value, e.g. None, but this needs a bit more work.

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