**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 *[[a, b], [c, d], [e, f]]* or *[[a, b, c], [d], [e,f]]*.

**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 g*roupBy*. **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:

`[[1, 2], [3, 4], [5, 6]]`

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:

```
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:

`[[1, 2], [3, 4], [5, 6], [7]]`

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:**

`[[1], [3], [8], [20], [23], [30]]`

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*.

**2.2. 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*:

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

This returns the list of pairs we want:

`[[0, "a"], [1, "b"], [2, "c"]]`

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

`[[0, "a"], [1, "b"], [2, "c"], [3]]`

**2.3. 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:

```
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.

**2.4. Using ***chunked*

**But, we can do this more elegantly with ***chunked*. So, let’s apply the method to our *evenList*:

`evenList.chunked(2)`

The *evenList* provides us with the pairs we want:

`[[0, "a"], [1, "b"], [2, "c"]]`

While the *unevenList* gives us the pairs and the extra element:

`[[0, "a"], [1, "b"], [2, "c"], [3]]`

**2.5. 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:

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

Both return the list without the separate element:

`[[0, "a"], [1, "b"], [2, "c"]]`

Finally, when we set *partialWindows* to *true* to include the partial result:

`unevenList.windowed(2, 2, true)`

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

`[[0, "a"], [1, "b"], [2, "c"], [3]]`

## 3. A Few Words About the *partition()* Function

We’ve solved our problem using *groupBy(), chunked(),* and *windowed()*. *partition()* is another function that can split a list into parts. As such, it can be pretty convenient for solving some similar problems.

**Kotlin’s ***partition()* function can split a list into a pair of two lists by a given predicate function.

An example can clarify this quickly. Let’s say we have a list of unsorted integers and we’d like to split it into two lists – one containing numbers less than 42, and the other holding numbers greater than or equal to 42.

If we solve this problem using *partition()*, it’s pretty easy:

```
val numbers = listOf(42, 1984, 1, 0, -4, 23, 100, 6, 8)
val aPairOfList = numbers.partition { it < 42 }
with(aPairOfList) {
assertEquals(listOf(1, 0, -4, 23, 6, 8), first)
assertEquals(listOf(42, 1984, 100), second)
}
```

As the example above shows, we provided *it < 42* as the predicate function. In the resulting pair, **the ***first* list carries all elements for which the predicate function yielded **true**, and the *second* list holds elements for which the predicate function returns *false*.

Sometimes, we want to use the split lists as variables directly instead of using them like *thePair.first* and *thePair.second. *In this case, **we can use a deconstructing declaration to assign ***partition()*‘s result to two list variables in one shot:

```
val (lessThan42, greaterThanOrEq42) = numbers.partition { it < 42 }
assertEquals(listOf(1, 0, -4, 23, 6, 8), lessThan42)
assertEquals(listOf(42, 1984, 100), greaterThanOrEq42)
```

**4. 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.

Finally, we talked about the *partition()* function through examples. It allows us easily to split a list into two based on a given predicate function.

As usual, the full source code is available over on GitHub.