1. Overview

When we work with Kotlin, one common task is flattening a list of lists. Simply put, this means converting a list of lists into a single list that contains all elements of the original lists.

In this tutorial, we’ll explore different ways to achieve this goal in Kotlin.

2. Introduction to the Problem

When we talk about flattening a list of lists, usually, there could be a couple of scenarios.

The first case is pretty straightforward – the elements of a list are lists too. An example can explain it quickly:

val listOfList = listOf(listOf("Kotlin", "Java"), listOf("Python", "Ruby"), listOf("C++", "Rust", "Go"))

As we can see, each element of listOfList is a string list. So, after we flatten listOfList, we’d like to have a list containing all those strings:

val expectedFlatList = listOf("Kotlin", "Java", "Python", "Ruby", "C++", "Rust", "Go")

The second scenario is that we’re given a list of instances of type X. Further, X contains a property Y in type List. So, the final goal is to get a list of Y objects. Again, let’s understand it through an example:

data class Team(val name: String, val members: List<String>)

As we can see in the code above, Team is a simple data class that has two properties. Further, the members property is a list of strings. Now, let’s say we have a list of three Team instances:

val teamA = Team("Team A", listOf("Kai", "Eric"))
val teamB = Team("Team B", listOf("Kevin", "Saajan"))
val teamC = Team("Team C", listOf("Milos", "Tom", "Jerry"))
val teamList = listOf(teamA, teamB, teamC)

Then, we want to have a list of all members of the Teams in teamList:

val expectedFlatMembers = listOf("Kai", "Eric", "Kevin", "Saajan", "Milos", "Tom", "Jerry")

Next, let’s see how to get the desired results in different ways. Of course, we’ll cover both scenarios.

For simplicity, we’ll use unit test assertions to verify if each approach works as expected.

3. Using the flatten() Function

Kotlin’s standard library has the flatten() function for Iterable and Array.

Moreover, Kotlin’s List interface is a subtype of the Iterable interface. Therefore, for the first scenario, we can call the flatten() function from the list of lists to get the result:

val result = listOfList.flatten()
assertEquals(expectedFlatList, result)

However, we cannot use flatten() to analyze the element’s type and extract the list properties we need. So, the flatten() function cannot solve the problem in the second scenario.

4. Using the flatMap() Function

The flatMap() function is another function defined in the Iterable interface. We can use it for flattening a list of lists too. Next, let’s see how it handles the first scenario:

val flatList = listOfList.flatMap { it }
assertEquals(expectedFlatList, flatList)

The flatMap() function takes a function as an argument that maps each list element to a new list and then flattens the result into a single list. In the first scenario, the list’s element is already a string list, so we just return it in the function.

However, if we look at the teamList example, in the flatMap{…} function, it will be a Team instance, so we need to navigate to the members list:

val flatMembers = teamList.flatMap { it.members }
assertEquals(expectedFlatMembers, flatMembers)

If we run the test, it passes.

5. Using the fold() Function

The fold() function in Kotlin is used to apply a function cumulatively to elements in a list from left to right. But we can use it to flatten a list of lists by applying the function that concatenates two lists. 

Next, let’s see how to flatten the list in both cases using the fold() function:

val flatList = listOfList.fold(listOf<String>()) { acc, str -> acc + str }
assertEquals(expectedFlatList, flatList)
val flatMembers = teamList.fold(listOf<String>()) { list, team -> list + team.members }
assertEquals(expectedFlatMembers, flatMembers)

6. Conclusion

In this article, we first talked about the two scenarios of flattening a list of lists in practice. Then, we learned three approaches through examples to perform the flattening operation on the two scenarios.

As usual, the implementation of all these examples is over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.