1. Introduction

A map is a collection of key-value pairs. In Kotlin, we can easily create maps using the mapOf() method. However, when working with maps, we’re often presented with situations that require us to transform them in some way. For instance, we may need to filter certain key-value pairs based on a particular condition, or we may need to modify certain values in the maps. In cases like this, it’s important to use efficient and idiomatic ways of transforming our Map.

In this tutorial, we’ll explore some idiomatic ways to transform a map in Kotlin, along with unit tests and explanations.

2. Using the mapValues() Method

The mapValues() method is used to transform the values of a map. It accepts a lambda function as an argument, which is applied to each value of the map. The result is a new map with the same keys as the original map but with the transformed values:

@Test
fun `transform map using mapValues() method`() {
    val map = mapOf("one" to 1, "two" to 2, "three" to 3, "four" to 4, "five" to 5)
    val transformedMap = map.mapValues { it.value * 2 }
    assertEquals(
        mapOf("one" to 2, "two" to 4, "three" to 6, "four" to 8, "five" to 10),
        transformedMap
    )
}

In this example, we construct a map and then transform it using mapValues(). The mapValues() method multiplies each value by two and, as such, we end up with a new map that has the same keys as the original map, but with values that are twice as big.

3. Using the mapKeys() Method

The mapKeys() method also creates a new map by applying a transformation or lambda function to the keys of the original map. It’s particularly useful when we want to transform only the keys of the map, without altering the values:

@Test
fun `transform map using mapKeys() method`() {
    val map = mapOf("one" to 1, "two" to 2, "three" to 3, "four" to 4, "five" to 5)
    val transformedMap = map.mapKeys { it.key.uppercase() }
        
    assertEquals(
        mapOf("ONE" to 1, "TWO" to 2, "THREE" to 3, "FOUR" to 4, "FIVE" to 5),
        transformedMap
    )
}

In the code above, we’re creating a new map by passing a lambda function to operate on the keys of the original map. This lambda uppercases all the keys of the original map. Finally, the resulting map has the same values as the original map, but the keys have been transformed.

4. Using the filterKeys() Method

Another interesting way to transform a map is by using the filterKeys() method. We can use this method to filter out certain key-value pairs from the map based on the keys. Similarly, it takes a predicate function that returns a boolean value and applies this function to each key of the map. Finally, our result is a new map with only those key-value pairs whose keys pass the predicate we provide the filterKeys() method:

@Test
fun `transform map using filterKeys() method`() {

    val map1 = mapOf("one" to 1, "two" to 2, "three" to 3, "four" to 4, "five" to 5)
    val map2 = mapOf(1 to 1, 2 to 2, 3 to 3, 4 to 4, 5 to 5)
        
    val transformedMap1 = map1.filterKeys { it != "three" }
    val transformedMap2 = map2.filterKeys { it % 2 == 0 }

    assertEquals(mapOf("one" to 1, "two" to 2, "four" to 4, "five" to 5), transformedMap1)
    assertEquals(mapOf(2 to 2, 4 to 4), transformedMap2)
}

In this example, we built two maps, one with keys as strings and the other with keys as integers. We filtered the first map to obtain a new map with the same key-value pairs from the original map except for the pair whose key is equal to “three”. Similarly, we filtered the second map to only keep the keys that are divisible by two.

5. Using the map() Method

Interestingly, the map() method provides a more general way to transform both the keys and values of the map. This method allows us to operate on each entry of the map. Similarly, it accepts a lambda function parameter that transforms the key-value pairs in the original map:

 @Test
fun `transform map using map() method`() {
    val map = mapOf("one" to 1, "two" to 2, "three" to 3, "four" to 4, "five" to 5)
    val transformedMap = map.map { it.key.uppercase() to it.value * 10 }.toMap()

    assertEquals(
        mapOf("ONE" to 10, "TWO" to 20, "THREE" to 30, "FOUR" to 40, "FIVE" to 50),
        transformedMap
    )
}

Here, we’re transforming both the keys and values of the original map. We then obtain a new map with uppercase keys and values that have been multiplied by ten.

6. Using the flatMap() and groupBy() Methods

Assuming we have a Map that associates a key to a List, we can also idiomatically transform such a map using flatMap() and groupBy().

Kotlin’s flatMap() method enables us to destructure a map into a new list by flattening it. It also accepts a lambda function as a parameter that transforms each key-value pair from the original map:

@Test
fun `transform map using flatMap() method`() {
    val map = mapOf("one" to listOf(1, 2), "two" to listOf(3, 4, 5), "three" to listOf(6, 7, 8, 9))

    val flattenedList = map.flatMap { (key, value) -> value.map { key to it } }
    val grouped = flattenedList.groupBy({ it.first }, { it.second })

    assertEquals("{one=[1, 2], two=[3, 4, 5], three=[6, 7, 8, 9]}", grouped.toString())
}

This example uses the flatMap() method to transform each entry in the map to a list of key-value pairs, where each key is the original key and each value is an element from the original list. We then hold the list of pairs in flattenedList. Subsequently, we use the groupBy() method to group the pairs by key and value, reassembling our original map of lists.

7. Using the associate() Method

Alternatively, we can use the associate() method to create a new map from scratch. It creates the new map by associating a key with a value as dictated by the lambda function:

@Test
fun `transform map using associate() method`() {
    val originalList = listOf("one", "two", "three", "four", "five")
    val transformedMap = originalList.associate { it to it.length }

    assertEquals(
        mapOf("one" to 3, "two" to 3, "three" to 5, "four" to 4, "five" to 4),
        transformedMap
    )
}

In this example, we’re also creating a new map from scratch by associating each element from the list as a key with its length as a value. Ultimately, the resulting map contains elements from the list as keys and the length of the original String as Int values.

This method allows us to construct a map even if the starting structure is not a map itself.

8. Conclusion

In this article, we’ve explored various idiomatic ways to transform maps in Kotlin using the standard library methods. We’ve covered map(), mapKeys(), mapValues(), flatMap(), and associate(), and provided examples of each with unit tests and explanations. These methods are powerful tools that can help us write concise and expressive code when working with maps in Kotlin.

As always, the code samples and relevant test cases for this article can be found over on GitHub.

Comments are closed on this article!