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:
Starting Scala 2.13
, in order to swap key/values without loosing keys associated to same values, we can use Map
s new groupMap method, which (as its name suggests) is an equivalent of a groupBy
and a map
ping over grouped items.
Map(1 -> "a", 2 -> "b", 4 -> "b").groupMap(_._2)(_._1)
// Map("b" -> List(2, 4), "a" -> List(1))
This:
group
s elements based on their second tuple part (_._2
) (group part of groupMap)
map
s grouped items by taking their first tuple part (_._1
) (map part of groupMap)
This can be seen as a one-pass version of map.groupBy(_._2).mapValues(_.map(_._1))
.
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))
We can try using this foldLeft
function that will take care of collisions and invert the map in single traversal.
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))
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
}
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]].