How to reverse a Map in Kotlin?

I am trying to reverse a Map in Kotlin. So far, I have come up with:

mapOf("foo" to 42) .toList() .map { (k, v) -> v to k } .toMap()

Is there any better way of doing this without using a middleman(middlelist)?

6 Answers

Since the Map consists of Entrys and it is not Iterable you can use Map#entries instead. It will be mapped to Map#entrySet to create a backed view of Set<Entry>, for example:

val reversed = map.entries.associateBy({ it.value }) { it.key }

OR use Iterable#associate, which will create additional Pairs.

val reversed = map.entries.associate{(k,v)-> v to k}

OR using Map#forEach:

val reversed = mutableMapOf<Int, String>().also { // v-- use `forEach` here map.forEach { (k, v) -> it.put(v, k) }
}.toMap()
// ^--- you can add `toMap()` to create an immutable Map.
4

Here is a simple extension function that reverse a map - without generating unneeded garbage (like pairs, intermediate data structures and unnecessary closures )

fun <K, V> Map<K, V>.reversed() = HashMap<V, K>().also { newMap -> entries.forEach { newMap.put(it.value, it.key) }
}

note that apply is inlined, and entries.forEach is also inlined (which is not the same for Map::forEach)

In case your map is not a 1-1 mapping and you want the inversion to be a list of values:

mapOf(1 to "AAA", 2 to "BBB", 3 to "BBB").toList() .groupBy { pair -> pair.second } // Pair<Int, String> .mapValues { entry -> entry.value.map { it.first } // Entry<String, List<Pair<Int, String>> }

If you need to reverse a multimap like m: Map<K, List<V>> to a Map<V, List<K>> you can do

m .flatMap { it.value.map { oneValue -> oneValue to it.key } } .groupBy({ it.first }, { it.second }) .toMap()

In sequence,

  • mapOf('a' to listOf('b', 'c'), 'd' to listOf('b'))gets flat mapped to a sequence like
  • listOf('b' to 'a', 'c' to 'a', 'b' to 'd') which gets grouped to
  • listOf('b' to listOf('a', 'd'), 'c' to listOf('a')) which then gets converted to a map.

This probably creates intermediate objects.

I'm still learning the ins and outs of Kotlin, but I had the same requirement and as of Kotlin 1.2 it appears that you can iterate over a Map and so map() it directly like this:

@Test
fun testThatReverseIsInverseOfMap() { val intMap = mapOf(1 to "one", 2 to "two", 3 to "three") val revMap = intMap.map{(k,v) -> v to k}.toMap() assertTrue(intMap.keys.toTypedArray() contentEquals revMap.values.toTypedArray()) assertTrue(intMap.values.toTypedArray() contentEquals revMap.keys.toTypedArray())
}
0

This is my take on a 1:1 map

 private fun <K, V> Map<K, V>.reverseOneToOneMap(): Map<V, K> { val result = this.entries.associateBy({ it.value }) { it.key } if (result.size != this.size) { throw RuntimeException("Map must be 1:1") } return result }

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

You Might Also Like