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

Ale*_*yMK 95 scala scala-collections

学习Scala当前需要反转Map来进行一些反向值 - >键查找.我正在寻找一种简单的方法来做到这一点,但只提出:

(Map() ++ origMap.map(kvp=>(kvp._2->kvp._1)))
Run Code Online (Sandbox Code Playgroud)

有人有更优雅的方法吗?

Dan*_*ral 166

假设值是唯一的,这适用:

(Map() ++ origMap.map(_.swap))
Run Code Online (Sandbox Code Playgroud)

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

origMap.map(_.swap)
Run Code Online (Sandbox Code Playgroud)

能够做到这一点是Scala 2.8有一个新的集合库的部分原因.

  • @dev-null 抱歉,您的示例不属于所需的假设。 (2认同)

Rok*_*alj 44

在数学上,映射可能不是可逆的(单射),例如,Map[A,B]你不能得到Map[B,A],而是得到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))
Run Code Online (Sandbox Code Playgroud)

  • `.map(_._ 1)`会更像".keys" (2认同)

Lee*_*oll 10

你可以在几个方面进行迭代时避免使用._1的东西.

这是一种方式.这使用了一个部分函数,​​它涵盖了对地图重要的唯一一个案例:

Map() ++ (origMap map {case (k,v) => (v,k)})
Run Code Online (Sandbox Code Playgroud)

这是另一种方式:

import Function.tupled        
Map() ++ (origMap map tupled {(k,v) => (v,k)})
Run Code Online (Sandbox Code Playgroud)

map迭代使用两个元素元组调用一个函数,而匿名函数需要两个参数.Function.tupled进行翻译.


Edd*_*son 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))
}
Run Code Online (Sandbox Code Playgroud)

其中oldMap的类型Map[A, Seq[B]]和newMap的类型Map[B, Seq[A]]

嵌套的foldLefts让我有点畏缩,但这是我能找到的最简单的方法来实现这种类型的反转.有人有更清洁的解决方案?


jwv*_*wvh 5

好的,这是一个非常老的问题,有很多好的答案,但是我已经制造了终极的,万事俱备的Swiss-Army刀,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))
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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())
}
Run Code Online (Sandbox Code Playgroud)

用法:

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()
Run Code Online (Sandbox Code Playgroud)

我希望将这两种方法都放在同一个隐式类中,但是花在我身上的时间越多,它看起来就越有问题。