Which Map Transformation Should I Use?
Tags: kotlin, android, androiddev, functional
Published: 2024-07-10 10:08:43

---
title: Which Map Transformation Should I Use?
published: true
date: 2024-07-04 05:00:00 UTC
tags: kotlin,android, androiddev, functional
canonical_url: https://darrylbayliss.net/which-map-transformation-should-i-use/
---

Map transformation functions find common usage in Android development. They are part of the [Kotlin Standard Library](https://kotlinlang.org/api/latest/jvm/stdlib/), a library built by JetBrains to provide standard functionality across Kotlin codebases.

Inside the Standard Library is a package called [kotlin.collections](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/), containing the building blocks for different collections. These include [Lists](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/), [Maps](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/#kotlin.collections.Map), and [Sets](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/).

The collections package also contain the map transformation functions. These functions take the contents of a collection and transform them into another collection containing the transformed state.

![Map transformation diagram](https://darrylbayliss.net/images/map_transformation_diagram.png)

Let’s take a look at some examples.

## The Map Transformation

The first transformation is the `map()` function.

```kotlin
  val numbersList = listOf(1, 2, 3)
  numbersList.map { it + 1 }.also(::println) // listOf(2, 3, 4)

  val numbersMap = mapOf("one" to 1, 
                         "two" to 2, 
                         "three" to 3)
  numbersMap.map { it.value + 1 }.also(::println) // listOf(2, 3, 4)

  val numbersSet = setOf(1, 2, 3)
  numbersSet.map { it + 1 }.also(::println) // listOf(2, 3, 4)

```

This function iterates over a collection and applies the transformation to each value within a lambda, before returning a new collection containing the transformed values.

`map()` is available across different types of collections. The reason for this is because `map()` is an extension function on the [Iterable](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-iterable/) interface. Most collections implement `Iterable`, meaning they can make use of the map function.

Map collection types are different. They don’t implement `Iterable`, instead they possess a separate [extension method](https://github.com/JetBrains/kotlin/blob/037b3697ed635a52c283da7b2bf6ecd0961ce8f4/libraries/stdlib/common/src/generated/_Maps.kt#L125) to provide a map function that iterates over each entry and returns a List of results.

## Map Transformations and Null Values

Map transformations come in different forms. Another useful transformation is the `mapNotNull()` function.

```kotlin
  val numbersList = listOf(1, 2, 3)
  numbersList.mapNotNull { if (it + 1 == 3) null else it + 1 }.also(::println) // listOf(2, 4)

  val numbersMap = mapOf("one" to 1, 
                         "two" to 2, 
                         "three" to 3)

  numbersMap.mapNotNull { if (it.value + 1 == 3) null else it.value + 1 }.also(::println) // listOf(2, 4)

  val numbersSet = setOf(1, 2, 3)
  numbersSet.mapNotNull { if (it + 1 == 3) null else it + 1 }.also(::println) // listOf(2, 4)

```

This function acts both as a transformation function and a filter for null values. If the transformation inside the lambda results in `null` then the value is not added to the new list.

`mapNotNull()` is available to collections implementing the `Iterable` interface.

Map types have their own [extension function](https://github.com/JetBrains/kotlin/blob/037b3697ed635a52c283da7b2bf6ecd0961ce8f4/libraries/stdlib/common/src/generated/_Maps.kt#L135) to provide similar functionality. Returning a list of results.

## Acquiring an Index with Map Transformations

If you need to know the location of the value within the collection being transformed you can use `mapIndexed()`.

```kotlin
  val numbersList = listOf(1, 2, 3)
  numbersList.mapIndexed { index, number -> number + index + 1 }.also(::println) // listOf(2, 4, 6)

  val numbersMap = mapOf("one" to 1, 
                         "two" to 2, 
                         "three" to 3)
  
  numbersMap.asIterable().mapIndexed { index, entry -> entry.value + index + 1 }.also(::println) // listOf(2, 4, 6)

  val numbersSet = setOf(1, 2, 3)
  numbersSet.mapIndexed { index, number -> number + index + 1 }.also(::println) // listOf(2, 4, 6)

```

Here the location of the value (the index) within the collection is passed alongside the value being transformed. `mapIndexed()` is available to collections implementing the `Iterable` interface.

Map types don’t have a `mapIndexed()` extension function. What you can do though is use the [asIterable()](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/as-iterable.html) extension to wrap the Map inside an Iterable instance. Then you can use `mapIndexed()` without issue.

If you need to check for null values and also require an index you can also use [mapIndexedNotNull()](https://github.com/JetBrains/kotlin/blob/037b3697ed635a52c283da7b2bf6ecd0961ce8f4/libraries/stdlib/common/src/generated/_Collections.kt#L1576).

```kotlin
  val numbersList = listOf(1, 2, 3)
  numbersList.mapIndexedNotNull { index, number -> if (number + 1 == 3) null else number + index + 1 }.also(::println) // listOf(2, 6)
  
  val numbersMap = mapOf("one" to 1, 
                         "two" to 2, 
                         "three" to 3)
  
  numbersMap.asIterable().mapIndexedNotNull { index, entry -> if (entry.value + 1 == 3) null else entry.value + index + 1 }.also(::println) // listOf(2, 6)

  val numbersSet = setOf(1, 2, 3)
  numbersSet.mapIndexedNotNull { index, number -> if (number + 1 == 3) null else number + index + 1 }.also(::println) //listOf(2, 6)

```

`mapIndexedNotNull()` works similarly to `mapNotNull()`. It filters away null values within the transformation lambda whilst also passing in the index for the value from the collection. Like other map transformations it exists on all types implementing Iterable.

Map types can use the `asIterable()` function to gain access to `mapIndexedNotNull()`.

## Other Transformations for Map Types

Map types work differently than other collections due to not implementing the [Collection](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-collection/) or Iterable interfaces. Because of this they have a few of their own transformation functions not available to other types. The first is called [mapKeys()](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/map-keys.html).

```kotlin
  val numbersMap = mapOf("one" to 1, 
                         "two" to 2, 
                         "three" to 3)
  
  numbersMap.mapKeys { entry -> entry.key.capitalize() }.also(::println) // mapOf("One" to 1, "Two" to 2, "Three" to 3)

```

`mapKeys()` transforms each key within the map by passing through each Entry of the map. Once all the transformations are complete they are applied to the Map.

The second function is called [mapValues()](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/map-values.html).

```kotlin
  val numbersMap = mapOf("one" to 1, 
                         "two" to 2, 
                         "three" to 3)
  
  numbersMap.mapValues { entry -> entry.value + 1 }.also(::println) // mapOf("one" to 2, "two" to 3, "three" to 4)

```

`mapValues()` works in a similar way. It passes through each `Entry` of the map and transforms each value. Once all the transformations are complete they are applied to the map.

## Passing Map Transformations to a Destination

If you want to pass applied transformations to a different collection other than the source there are a few functions to help.

```kotlin
  val numbersList = listOf(1, 2, 3)
  val numbersDestinationSet = mutableSetOf<Int>()
  numbersList.mapTo(numbersDestinationSet) { it + 1 }
  println(numbersDestinationList) // setOf(2, 3, 4)

  val numbersMap = mapOf("one" to 1, 
                         "two" to 2, 
                         "three" to 3)
  val numbersDestinationList2 = mutableListOf<Int>()
  numbersList.mapTo(numbersDestinationList2) { it + 1 }
  println(numbersDestinationList2) // listOf(2, 3, 4)

  val numbersSet = setOf(1, 2, 3)
  val numbersDestinationList = mutableListOf<Int>()
  numbersSet.mapTo(numbersDestinationList) { it + 1 }
  println(numbersDestinationList) // listOf(2, 3, 4)

```

The `mapTo` functions work similarly to `map()`. The difference is they write the transformations to the passed in collection. The collection being written doesn’t have to be the same type as the source collection. Useful if you have a usecase where a different collection would be more optimal.

Map types can’t use `mapTo()`. This is because `mapTo()` expects to write to a [MutableCollection](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-mutable-collection/#kotlin.collections.MutableCollection), which Map types don’t inherit from.

There is a `MutableMap` type, however because it doesn’t inherit from `MutableCollection` there is no `mapTo()` extension available.

## More Resources

If you’d like to learn more about Kotlin’s transformation methods. I highly recommend [this page](https://kotlinlang.org/docs/collection-transformations.html) on collection transformations from the Kotlin language documentation.

As well as covering map transformations it also covers other methods like zipping, association and flattening. These topics are beyond the scope of this post however are nevertheless useful to understand.