Map transformation functions find common usage in Android development. They are part of the Kotlin Standard Library, a library built by JetBrains to provide standard functionality across Kotlin codebases.
Inside the Standard Library is a package called kotlin.collections, containing the building blocks for different collections. These include Lists, Maps, and Sets.
The collections package also contains the map transformation functions. These functions take the contents of a collection and transform them into another collection containing the transformed state.
Let’s take a look at some examples.
The first transformation is the map()
function.
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 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 to provide a map function that iterates over each entry and returns a List of results.
Map transformations come in different forms. Another useful transformation is the mapNotNull()
function.
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 to provide similar functionality. Returning a list of results.
If you need to know the location of the value within the collection being transformed, you can use mapIndexed()
.
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() 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().
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()
.
Map types work differently than other collections due to not implementing the 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().
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().
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.
If you want to pass applied transformations to a different collection other than the source, there are a few functions to help.
val numbersList = listOf(1, 2, 3)
val numbersDestinationSet = mutableSetOf<Int>()
numbersList.mapTo(numbersDestinationSet) { it + 1 }
println(numbersDestinationSet) // setOf(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, 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.
If you’d like to learn more about Kotlin’s transformation methods. I highly recommend this page 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, they are nevertheless useful to understand.