Elegant way to invert a map in Scala

前端 未结 10 554
慢半拍i
慢半拍i 2020-12-02 13:49

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:



        
相关标签:
10条回答
  • 2020-12-02 14:24

    Assuming values are unique, this works:

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

    On Scala 2.8, however, it's easier:

    origMap.map(_.swap)
    

    Being able to do that is part of the reason why Scala 2.8 has a new collection library.

    0 讨论(0)
  • 2020-12-02 14:25

    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.

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

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

    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?

    0 讨论(0)
  • 2020-12-02 14:27

    In 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)
    
    0 讨论(0)
  • 2020-12-02 14:34

    You can avoid the ._1 stuff while iterating in few ways.

    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.

    0 讨论(0)
  • 2020-12-02 14:36

    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:

    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))
    
    0 讨论(0)
  • 2020-12-02 14:39

    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, Map inverter and this is the place to post it.

    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.

    0 讨论(0)
提交回复
热议问题