在 Scala 中反转地图的优雅方式
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2338282/
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
Elegant way to invert a map in Scala
提问by AlexeyMK
Learning Scala currently and needed to invert a Map to do some inverted value->key lookups. I was looking for a simple way to do this, but came up with only:
目前正在学习 Scala,需要反转 Map 以进行一些反转的 value->key 查找。我正在寻找一种简单的方法来做到这一点,但只想出了:
(Map() ++ origMap.map(kvp=>(kvp._2->kvp._1)))
Anybody have a more elegant approach?
有人有更优雅的方法吗?
回答by Daniel C. Sobral
Assuming values are unique, this works:
假设值是唯一的,这有效:
(Map() ++ origMap.map(_.swap))
On Scala 2.8, however, it's easier:
然而,在 Scala 2.8 上,它更容易:
origMap.map(_.swap)
Being able to do that is part of the reason why Scala 2.8 has a new collection library.
能够做到这一点是 Scala 2.8 拥有新集合库的部分原因。
回答by Rok Kralj
Mathematically, the mapping might not be invertible (injective), e.g., from Map[A,B], you can't get Map[B,A], but rather you get Map[B,Set[A]], because there might be different keys associated with same values. So, if you are interested in knowing all the keys, here's the code:
在数学上,映射可能不可逆(内射),例如, from Map[A,B],您不能 get Map[B,A],而是 get Map[B,Set[A]],因为可能有与相同值关联的不同键。所以,如果你有兴趣知道所有的键,这里是代码:
scala> val m = Map(1 -> "a", 2 -> "b", 4 -> "b")
scala> m.groupBy(_._2).mapValues(_.keys)
res0: Map[String,Iterable[Int]] = Map(b -> Set(2, 4), a -> Set(1))
回答by Lee Mighdoll
You can avoid the ._1 stuff while iterating in few ways.
您可以通过几种方式在迭代时避免 ._1 的东西。
Here's one way. This uses a partial function that covers the one and only case that matters for the map:
这是一种方法。这使用了一个偏函数,它涵盖了对地图重要的唯一情况:
Map() ++ (origMap map {case (k,v) => (v,k)})
Here's another way:
这是另一种方式:
import Function.tupled
Map() ++ (origMap map tupled {(k,v) => (v,k)})
The map iteration calls a function with a two element tuple, and the anonymous function wants two parameters. Function.tupled makes the translation.
map 迭代调用一个带有两个元素元组的函数,而匿名函数需要两个参数。Function.tupled 进行翻译。
回答by Eddie Carlson
I came here looking for a way to invert a Map of type Map[A, Seq[B]] to Map[B, Seq[A]], where each B in the new map is associated with every A in the old map for which the B was contained in A's associated sequence.
我来这里是为了寻找一种将 Map[A, Seq[B]] 类型的 Map 反转为 Map[B, Seq[A]] 的方法,其中新地图中的每个 B 都与旧地图中的每个 A 相关联其中 B 包含在 A 的关联序列中。
E.g.,Map(1 -> Seq("a", "b"), 2-> Seq("b", "c"))
would invert to Map("a" -> Seq(1), "b" -> Seq(1, 2), "c" -> Seq(2))
例如,Map(1 -> Seq("a", "b"), 2-> Seq("b", "c"))
将反转为Map("a" -> Seq(1), "b" -> Seq(1, 2), "c" -> Seq(2))
Here's my solution :
这是我的解决方案:
val newMap = oldMap.foldLeft(Map[B, Seq[A]]().withDefaultValue(Seq())) {
case (m, (a, bs)) => bs.foldLeft(m)((map, b) => map.updated(b, m(b) :+ a))
}
where oldMap is of type Map[A, Seq[B]]and newMap is of type Map[B, Seq[A]]
其中 oldMap 是类型Map[A, Seq[B]], newMap 是类型Map[B, Seq[A]]
The nested foldLefts make me cringe a little bit, but this is the most straightforward way I could find to accomplish this type of inversion. Anyone have a cleaner solution?
嵌套的 foldLefts 让我有点畏缩,但这是我能找到的最直接的方法来完成这种类型的反转。有人有更清洁的解决方案吗?
回答by jwvh
OK, so this is a very old question with many good answers, but I've built the ultimate, be-all-and-end-all, Swiss-Army-knife, Mapinverter and this is the place to post it.
好的,所以这是一个非常古老的问题,有很多很好的答案,但我已经构建了终极的,万能的,瑞士军刀,Map逆变器,这是发布它的地方。
It's actually two inverters. One for individual value elements...
它实际上是两个逆变器。一种用于单个值元素...
//from Map[K,V] to Map[V,Set[K]], traverse the input only once
implicit class MapInverterA[K,V](m :Map[K,V]) {
def invert :Map[V,Set[K]] =
m.foldLeft(Map.empty[V, Set[K]]) {
case (acc,(k, v)) => acc + (v -> (acc.getOrElse(v,Set()) + k))
}
}
...and another, quite similar, for value collections.
...和另一个非常相似的值集合。
import scala.collection.generic.CanBuildFrom
import scala.collection.mutable.Builder
import scala.language.higherKinds
//from Map[K,C[V]] to Map[V,C[K]], traverse the input only once
implicit class MapInverterB[K,V,C[_]](m :Map[K,C[V]]
)(implicit ev :C[V] => TraversableOnce[V]) {
def invert(implicit bf :CanBuildFrom[Nothing,K,C[K]]) :Map[V,C[K]] =
m.foldLeft(Map.empty[V, Builder[K,C[K]]]) {
case (acc, (k, vs)) =>
vs.foldLeft(acc) {
case (a, v) => a + (v -> (a.getOrElse(v,bf()) += k))
}
}.mapValues(_.result())
}
usage:
用法:
Map(2 -> Array('g','h'), 5 -> Array('g','y')).invert
//res0: Map(g -> Array(2, 5), h -> Array(2), y -> Array(5))
Map('q' -> 1.1F, 'b' -> 2.1F, 'c' -> 1.1F, 'g' -> 3F).invert
//res1: Map(1.1 -> Set(q, c), 2.1 -> Set(b), 3.0 -> Set(g))
Map(9 -> "this", 8 -> "that", 3 -> "thus", 2 -> "thus").invert
//res2: Map(this -> Set(9), that -> Set(8), thus -> Set(3, 2))
Map(1L -> Iterator(3,2), 5L -> Iterator(7,8,3)).invert
//res3: Map(3 -> Iterator(1, 5), 2 -> Iterator(1), 7 -> Iterator(5), 8 -> Iterator(5))
Map.empty[Unit,Boolean].invert
//res4: Map[Boolean,Set[Unit]] = Map()
I would prefer to have both methods in the same implicit class but the more time I spent looking into it the more problematic it appeared.
我更喜欢在同一个隐式类中使用这两种方法,但我花的时间越多,它出现的问题就越多。
回答by hohonuuli
You could invert a map using:
您可以使用以下方法反转地图:
val i = origMap.map({case(k, v) => v -> k})
The problem with this approach is that if your values, which have now become the hash keys in your map, are not unique you will drop the duplicate values. To illustrate:
这种方法的问题在于,如果您的值(现在已成为映射中的哈希键)不是唯一的,您将删除重复的值。为了显示:
scala> val m = Map("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 1)
m: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, c -> 3, d -> 1)
// Notice that 1 -> a is not in our inverted map
scala> val i = m.map({ case(k , v) => v -> k})
i: scala.collection.immutable.Map[Int,String] = Map(1 -> d, 2 -> b, 3 -> c)
To avoid this you can convert your map to a list of tuples first, then invert, so that you don't drop any duplicate values:
为避免这种情况,您可以先将映射转换为元组列表,然后反转,这样就不会删除任何重复值:
scala> val i = m.toList.map({ case(k , v) => v -> k})
i: List[(Int, String)] = List((1,a), (2,b), (3,c), (1,d))
回答by y?s??la
In scala REPL:
在 Scala REPL 中:
scala> val m = Map(1 -> "one", 2 -> "two")
m: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> one, 2 -> two)
scala> val reversedM = m map { case (k, v) => (v, k) }
reversedM: scala.collection.immutable.Map[java.lang.String,Int] = Map(one -> 1, two -> 2)
Note that duplicate values will be overwritten by the last addition to the map:
请注意,重复的值将被地图的最后一次添加覆盖:
scala> val m = Map(1 -> "one", 2 -> "two", 3 -> "one")
m: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> one, 2 -> two, 3 -> one)
scala> val reversedM = m map { case (k, v) => (v, k) }
reversedM: scala.collection.immutable.Map[java.lang.String,Int] = Map(one -> 3, two -> 2)
回答by Xavier Guihot
Starting Scala 2.13, in order to swap key/values without loosing keys associated to same values, we can use Maps new groupMapmethod, which (as its name suggests) is an equivalent of a groupByand a mapping over grouped items.
首先Scala 2.13,为了在不丢失与相同值关联的键的情况下交换键/值,我们可以使用Map新的groupMap方法,它(顾名思义)相当于对分组项目的 agroupBy和mapping。
Map(1 -> "a", 2 -> "b", 4 -> "b").groupMap(_._2)(_._1)
// Map("b" -> List(2, 4), "a" -> List(1))
This:
这:
groups elements based on their second tuple part (_._2) (group part of groupMap)maps grouped items by taking their first tuple part (_._1) (map part of groupMap)
groups 元素基于它们的第二个元组部分 (_._2) (组Map 的组部分)map通过采取他们的第一个元组部(S分组的项目_._1)(图组的一部分地图)
This can be seen as a one-pass versionof map.groupBy(_._2).mapValues(_.map(_._1)).
这可以被看作是一个通版的map.groupBy(_._2).mapValues(_.map(_._1))。
回答by Pavithran Ramachandran
We can try using this foldLeftfunction that will take care of collisions and invert the map in single traversal.
我们可以尝试使用这个foldLeft函数来处理碰撞并在单次遍历中反转地图。
scala> def invertMap[A, B](inputMap: Map[A, B]): Map[B, List[A]] = {
| inputMap.foldLeft(Map[B, List[A]]()) {
| case (mapAccumulator, (value, key)) =>
| if (mapAccumulator.contains(key)) {
| mapAccumulator.updated(key, mapAccumulator(key) :+ value)
| } else {
| mapAccumulator.updated(key, List(value))
| }
| }
| }
invertMap: [A, B](inputMap: Map[A,B])Map[B,List[A]]
scala> val map = Map(1 -> 2, 2 -> 2, 3 -> 3, 4 -> 3, 5 -> 5)
map: scala.collection.immutable.Map[Int,Int] = Map(5 -> 5, 1 -> 2, 2 -> 2, 3 -> 3, 4 -> 3)
scala> invertMap(map)
res0: Map[Int,List[Int]] = Map(5 -> List(5), 2 -> List(1, 2), 3 -> List(3, 4))
scala> val map = Map("A" -> "A", "B" -> "A", "C" -> "C", "D" -> "C", "E" -> "E")
map: scala.collection.immutable.Map[String,String] = Map(E -> E, A -> A, B -> A, C -> C, D -> C)
scala> invertMap(map)
res1: Map[String,List[String]] = Map(E -> List(E), A -> List(A, B), C -> List(C, D))
回答by Ashwin
Inverse is a better name for this operation than reverse (as in "inverse of a mathematical function")
I often do this inverse transformation not only on maps but on other (including Seq) collections. I find it best not to limit the definition of my inverse operation to one-to-one maps. Here's the definition I operate with for maps (please suggest improvements to my implementation).
def invertMap[A,B]( m: Map[A,B] ) : Map[B,List[A]] = { val k = ( ( m values ) toList ) distinct val v = k map { e => ( ( m keys ) toList ) filter { x => m(x) == e } } ( k zip v ) toMap }
Inverse 是此操作的比 reverse 更好的名称(如“数学函数的逆”)
我经常不仅在地图上而且在其他(包括 Seq)集合上进行这种逆变换。我发现最好不要将逆运算的定义限制为一对一映射。这是我用于地图的定义(请对我的实现提出改进建议)。
def invertMap[A,B]( m: Map[A,B] ) : Map[B,List[A]] = { val k = ( ( m values ) toList ) distinct val v = k map { e => ( ( m keys ) toList ) filter { x => m(x) == e } } ( k zip v ) toMap }
If it's a one-to-one map, you end up with singleton lists which can be trivially tested and transformed to a Map[B,A] rather than Map[B,List[A]].
如果是一对一映射,则最终会得到单例列表,可以对其进行简单测试并转换为 Map[B,A] 而不是 Map[B,List[A]]。

