在Scala中反转地图的优雅方式


97

当前正在学习Scala,需要反转Map来执行一些反转的value-> key查找。我一直在寻找一种简单的方法来做到这一点,但只想出了:

(Map() ++ origMap.map(kvp=>(kvp._2->kvp._1)))

有人有更优雅的方法吗?

Answers:


174

假设值是唯一的,则可以这样做:

(Map() ++ origMap.map(_.swap))

但是,在Scala 2.8上,它更容易:

origMap.map(_.swap)

能够做到这一点是Scala 2.8拥有新收藏库的部分原因。


1
小心!您可以使用此解决方案失去价值。例如,Map(1 -> "A", 2 -> "B", 3 -> "B").map(_.swap)结果Map(A -> 1, B -> 3)
dev-null

1
@ dev-null对不起,您的示例不属于要求的假设。
Daniel C. Sobral

我同意。抱歉,我忽略了这一点。
dev-null,

45

从数学上讲,映射可能不是可逆的(内射式),例如from Map[A,B],您不能获取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))

2
.map(_._1)会更容易听.keys
cheezsteak

现在感谢您,甚至缩短了几个字符。现在的区别是您得到Sets而不是List以前的s。
Rok Kralj

1
使用时要小心,.mapValues因为它会返回视图。有时,这就是您想要的,但是如果您不小心,它可能会消耗大量内存和CPU。要强制它变成一个地图,你可以做m.groupBy(_._2).mapVaues(_.keys).map(identity),或者你可以更换呼叫.mapValues(_.keys).map { case (k, v) => k -> v.keys }
Mark T.

10

您可以通过几种方式避免使用._1东西。

这是一种方法。这使用了一个部分功能,该功能涵盖了唯一重要的情况:

Map() ++ (origMap map {case (k,v) => (v,k)})

这是另一种方式:

import Function.tupled        
Map() ++ (origMap map tupled {(k,v) => (v,k)})

映射迭代调用具有两个元素元组的函数,匿名函数需要两个参数。Function.tupled进行翻译。


6

我来这里是为了寻找一种将Map [A​​,Seq [B]]类型的Map转换为Map [B,Seq [A]]的方法,其中新映射中的每个B与旧映射中的每个A相关联其中B包含在A的关联序列中。

例如,
Map(1 -> Seq("a", "b"), 2-> Seq("b", "c"))
将转化为
Map("a" -> Seq(1), "b" -> Seq(1, 2), "c" -> Seq(2))

这是我的解决方案:

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

其中oldMap是类型,Map[A, Seq[B]]而newMap是类型Map[B, Seq[A]]

嵌套的foldLefts使我有些畏缩,但这是我可以找到的最简单的方法来完成这种类型的反转。有人有更清洁的解决方案吗?


非常好的解决方案!@Rok,他的解决办法是有点不同的比你一点我想是因为他变换:Map[A, Seq[B]]Map[B, Seq[A]]您的解决方案trasnforms Map[A, Seq[B]]Map[Seq[B], Seq[A]]
YH

1
在这种情况下,没有两个嵌套的折叠并且可能会有更高的性能:a.toSeq.flatMap { case (a, b) => b.map(_ -> a) }.groupBy(_._2).mapValues(_.map(_._1))
Rok Kralj 2015年

6

好的,所以这是一个非常老的问题,有很多好的答案,但是我制造了终极的,万事俱备的瑞士军刀Map逆变器,这是发布它的地方。

实际上是两个逆变器。一个用于单个价值要素...

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

...和另一个非常相似的价值收集。

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

用法:

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

我宁愿将这两种方法都放在同一个隐式类中,但是花在研究它上的时间越多,它出现的问题就越多。


3

您可以使用以下方法反转地图:

val i = origMap.map({case(k, v) => v -> k})

这种方法的问题在于,如果您的值(现在已成为地图中的哈希键)不唯一,则将删除重复的值。为了显示:

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)

为避免这种情况,您可以先将地图转换为元组列表,然后再转换,以免删除任何重复的值:

scala> val i = m.toList.map({ case(k , v) => v -> k})
i: List[(Int, String)] = List((1,a), (2,b), (3,c), (1,d))

1

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

请注意,重复值将被映射的最后添加项覆盖:

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)

1

从开始Scala 2.13,为了交换键/值而不丢失与相同值关联的键,我们可以使用Maps的新groupMap方法,该方法(顾名思义)等效于a groupBymap对分组项目的ping操作。

Map(1 -> "a", 2 -> "b", 4 -> "b").groupMap(_._2)(_._1)
// Map("b" -> List(2, 4), "a" -> List(1))

这个:

  • groups个元素基于其第二元组部分(_._2)( Map的组部分)

  • map通过采取他们的第一个元组部(S分组的项目_._1)(图组的一部分地图

这可以被看作是一个通版map.groupBy(_._2).mapValues(_.map(_._1))


太糟糕了,它不会转换Map[K, C[V]]Map[V, C[K]]
jwvh

0
  1. 对于此运算,“逆”比“逆”更好(如“数学函数的逆”中所述)

  2. 我经常不仅在地图上而且在其他(包括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
    }

如果它是一对一的地图,那么您将得到单例列表,可以对其进行简单测试并将其转换为Map [B,A]而不是Map [B,List [A]]。


0

我们可以尝试使用此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 using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.