1. Introduction
As Kotlin developers, we often use Lists and Maps as core data structures to address different programming challenges. Sometimes, we might need to process a list of maps to obtain a different map that groups data by keys. That is, we need to convert a list of maps to a map of lists.
In this tutorial, we’ll explore various ways of converting a list of maps to a map grouped by keys in Kotlin.
2. Problem Explanation
To better understand the problem we’re trying to solve, let’s consider this sample data with input and expected output for all examples:
val listOfMaps = listOf(
mapOf("name" to "Albert", "age" to "18"),
mapOf("name" to "Naomi", "age" to "26"),
mapOf("name" to "Dru", "age" to "18"),
mapOf("name" to "Steve", "age" to "30")
)
val expectedMap = mapOf(
"name" to listOf("Albert", "Naomi", "Dru", "Steve"),
"age" to listOf("18", "26", "18", "30")
)
This code representation shows that the variable listOfMaps is a list that contains maps with a key-value pair. However, we want to combine all these maps into a single map that groups entries by keys to end up with expectedMap. Therefore, this new Map uses all the keys from the original listOfMaps and associates each key to a List of values. These represent all the values associated with a particular key in the original listOfMaps.
3. Using a for() Loop
A straightforward way to convert a List of Maps to a Map grouped by keys is to use a simple for() loop:
fun groupByUsingForLoop(input: List<Map<String, String>>): Map<String, List<String>> {
val result = mutableMapOf<String, MutableList<String>>()
for (map in input) {
for ((key, value) in map) {
result.getOrPut(key) { mutableListOf() }.add(value)
}
}
return result
}
Our helper method defines a MutableMap that denotes the map that will hold the grouped data. Moreover, it accepts a list of values from the original maps.
First, we create an empty mutable map to store the result. Then, we iterate over each map in the input list. For each Map, we also iterate over its entries using a nested for() loop. Subsequently, we use the getOrPut() method to either get the current list of values associated with the key or create a new empty list if the key is not yet present in the result map. Finally, we add the current value to the list.
As usual, it is a good practice to unit test our code to ensure that it operates as we anticipate:
@Test
fun `converts list of maps to maps grouped by key using for loop`() {
assertEquals(expectedMap, groupByUsingForLoop(listOfMaps))
}
Now, we’ll look at some built-in ways we can approach this problem. Kotlin’s standard library offers a groupBy() method that can group elements of a collection by a key. In addition, this method accepts a lambda function that produces a key for each element in the list. As a result, the value of each entry in the resulting Map is collected to a list of elements that have the same original key:
@Test
fun `converts list of maps to maps grouped by key using groupBy method`() {
val result = listOfMaps
.flatMap { map -> map.entries }
.groupBy({ it.key }, { it.value })
assertEquals(expectedMap, result)
}
In the code above, we take the input list of maps and use the flatMap() method to flatten the list of maps into a list of entries. Next, we use the groupBy() method to group the entries by their keys and create a map of lists. Finally, it creates a map that uses the keys from the original list of maps as its own keys. Each key is associated with a list of values that were associated with that key in the original input list.
5. Using the fold() Method
Additionally, we can use the fold() method to group a list of maps by a particular key. This method accumulates the entries of the map into a map of lists. Indeed, it accepts an initial map and a lambda function and applies it to each entry of the input list:
fun groupByUsingFoldMethod(input: List<Map<String, String>>): Map<String, List<String>> {
return input.fold(emptyMap()) { acc, map ->
acc.keys.union(map.keys).associateWith { key ->
acc.getOrDefault(key, emptyList()) + map.getOrDefault(key, "")
}
}
}
Our helper method accepts a list of maps as input. The fold() method starts with an empty map. Each map in the input list pairs the keys of the current map with the keys of the accumulator map.
Additionally, we use the associateWith() method to associate each key with a list of values. The list contains the values of the key in the accumulator map and the current map. As a result, the final map has all the keys from the original list of entries as its keys. Specifically, each key is associated with a list of all the values that were associated with that key in the original list of entries.
As usual, let’s unit-test this method for correctness:
@Test
fun `converts list of maps to maps grouped by key using fold method`() {
assertEquals(expectedMap, groupByUsingFoldMethod(listOfMaps))
}
Through the use of our helper method, we can guarantee that we obtain a map that precisely associates all keys to a list of values.