Elegant way to invert a map in Scala

前端 未结 10 598
慢半拍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: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.

提交回复
热议问题