Split a List into Parts in Kotlin

1. Introduction

Let’s say we have an array like [a, b, c, d, e, f] and we want to split the elements up into separate groups, like or .

In this tutorial, we’ll achieve this while examining some differences between Kotlin’s groupBy, chunked, and windowed.

2. Splitting a List into a List of Pairs

For our examples, we’ll use two lists – one with an even number of elements and one with an odd number of elements:

val evenList = listOf(0, "a", 1, "b", 2, "c");
val unevenList = listOf(0, "a", 1, "b", 2, "c", 3);

Clearly, we can divide our evenList into exactly three pairs. However, our unevenList will have one extra element.

In the remainder of this section, we’ll see various implementations for splitting our two lists, including how they deal with the extra element in unevenList.

2.1. Using groupBy

First, let’s implement a solution with groupBy. We’ll create a list with ascending numbers and use groupBy to split them:

val numberList = listOf(1, 2, 3, 4, 5, 6);
numberList.groupBy { (it + 1) / 2 }.values

This gives the desired result:

[[How does it work? Well, _groupBy_ executes the supplied function _(it + 1) / 2_ on every element:

* (1 + 1) / 2 = 1
* (2 + 1) / 2 = 1.5, which is rounded to 1
* (3 + 1) / 2 = 2
* (4 + 1) / 2 = 2.5, which is rounded to 2
* (5 + 1) / 2 = 3
* (6 + 1) / 2 = 3.5, which is rounded to 3

Then, _groupBy_ groups the elements in the list that gave the same result.

Now, when we do the same with an uneven list:

[source,java,gutter:,true]

val numberList = listOf(1, 2, 3, 4, 5, 6, 7);
numberList.groupBy { (it + 1) / 2 }.values

We get all the pairs and one extra element:

[source,java,gutter:,true]

[[But, if we go a bit further with some random numbers:

val numberList = listOf(1, 3, 8, 20, 23, 30);
numberList.groupBy { (it + 1) / 2 }.values

We’ll get something that is completely undesired:

[[The reason is simple; applying the _(it + 1) / 2_ function on every element gives: _1, 2, 4, 10, 12, 15_. All the results differ, so no elements are grouped together.

When we use our _evenList_ or _unevenList_, it's even worse — *the code doesn't compile*, as the function cannot be applied to _Strings_.

[[using-groupby-and-withindex]]
==== *2.2. Using _groupBy_ and _withIndex_*

[[using-groupby-and-withindex]]Really, if we want to group an arbitrary list into pairs, *we don't want to modify the __value __by our function, but the _index_:*

[source,java,gutter:,true]

evenList.withIndex()
.groupBy { it.index / 2 }
.map { it.value.map { it.value } }

This returns the list of pairs we want:

[source,java,gutter:,true]

[[a”], [1, “b”], [2, “c”]]

Furthermore, if we use the _unevenList_, we even get our separate element:

[source,java,gutter:,true]

[[a”], [1, “b”], [2, “c”], [3]]

[[using-groupby-with-foldindexed]]
==== *2.3. Using _groupBy_ With _foldIndexed_*

[[using-groupby-with-foldindexed]]We can go a step further than just using _index_ and program a bit more with _foldIndexed_ to save some allocations:

[source,java,gutter:,true]

evenList.foldIndexed(ArrayList<ArrayList<Any>>(evenList.size / 2)) { index, acc, item →
if (index % 2 == 0) {
acc.add(ArrayList(2))
}
acc.last().add(item)
acc
}

While a bit more verbose, *the _foldIndexed_ solution simply performs the operation on each element*, whereas the _withIndex_ function first creates an iterator and wraps each element.

[[using-chunked]]
==== *2.4. Using _chunked_*

[[using-chunked]]*But, we can do this more elegantly with _chunked_.* So, let's apply the method to our _evenList_:

[source,java,gutter:,true]

evenList.chunked(2)

The _evenList_ provides us with the pairs we want:

[source,java,gutter:,true]

[[a”], [1, “b”], [2, “c”]]

While the _unevenList_ gives us the pairs and the extra element:

[source,java,gutter:,true]

[[a”], [1, “b”], [2, “c”], [3]]

[[using-windowed]]
==== *2.5. Using _windowed_*

[[using-windowed]]*And _chunked_ works really well, but sometimes we need a bit more control.*

For instance, we may need to specify if we want only pairs, or if we want to include the extra element. *The _windowed_ method provides us with a _partialWindows_ Boolean*, which indicates if we want the partial result or not.

By default, _partialWindows_ is _false_. So, the following statements produce the same result:

[source,java,gutter:,true]

evenList.windowed(2, 2)
unevenList.windowed(2, 2, false)

Both return the list without the separate element:

[source,java,gutter:,true]

[[a”], [1, “b”], [2, “c”]]

Finally, when we set _partialWindows_ to _true_ to include the partial result:

[source,java,gutter:,true]

unevenList.windowed(2, 2, true)

We'll get the list of pairs plus the separate element:

[source,java,gutter:,true]

[[a”], [1, “b”], [2, “c”], [3]]

=== *3. Conclusion*

[[conclusion]]Using _groupBy_ is a nice programming exercise, but it can be quite error-prone. Some of the errors can be resolved simply by using an _index_.

To optimize the code, we can even use _foldIndexed_. However, this results in even more code. Luckily, the _chunked_ method offers us the same functionality out-of-the-box.

Moreover, the _windowed_ method provides additional configuration options. *If possible, it's best to use the _chunked_ method, and if we need additional configuration, we should use the _windowed_ method.*

As usual, the full source code is available over on https://github.com/eugenp/tutorials/tree/master/core-kotlin-2[GitHub].

Leave a Reply

Your email address will not be published.