Kotlin groupBy() and partition() Explained

In this blog post, I will explain Kotlin's groupBy() and partition() functions along with a few possible use-cases.
A featured image for category: Kotlin

1. Introduction

There’s no doubt that Kotlin’s creators put in a lot of effort in making it clear and concise programming language. Whatsoever, it brings to the table plenty of useful features and functions, which help us in our everyday life.

In this article, I will explain the two of them- Kotlin’s groupBy() and partition(). We will see how do these two can be applied in our code and what use cases do they help with.

2. Prepare Some Code

But before we start any considerations, let’s implement the necessary code to work with.

Let’s add the Item.kt class:

data class Item(
  val name: String,
  val type: ItemType
)

enum class ItemType {
  STANDARD, PREMIUM, OTHER
}

Following, let’s implement a simple list populated with Item instances:

val items = listOf(
  Item(name = "Item #1", type = ItemType.STANDARD),
  Item(name = "Item #2", type = ItemType.OTHER),
  Item(name = "Item #3", type = ItemType.PREMIUM),
  Item(name = "Item #4", type = ItemType.STANDARD),
  Item(name = "Item #5", type = ItemType.PREMIUM)
)

As we can see, this is a simple, read-only list containing different items. In real-life scenarios, it could be a list fetched from the database, or other external source.

3. groupBy()

With all that being done, let’s start with the first function of today’s Kotlin groupBy() and partition() comparison.

As the name suggests, this function allows us to group elements of the array by the key returned by the keySelector (which we need to pass). As a result, it returns a map, where each key has associated a map of values from the mapped array.

3.1. Simple groupBy()

To better understand it, let’s see the first example:

val groupedByItems = items.groupBy { it.type }
println(groupedByItems)

If we run it, we should see the following output (formatted manually for the purpose of readability):

{
  STANDARD=[Item(name=Item #1, type=STANDARD), Item(name=Item #4, type=STANDARD)], 
  OTHER=[Item(name=Item #2, type=OTHER)], 
  PREMIUM=[Item(name=Item #3, type=PREMIUM), Item(name=Item #5, type=PREMIUM)]
}

We can clearly see that our list was converted to the Map<ItemType, List<Item>>.

Additionally, we can use Kotlin’s property references to make our code even clearer:

val groupedByItemsReference = items.groupBy(Item::type)
println(groupedByItemsReference)

And the result remains exactly the same:

{
  STANDARD=[Item(name=Item #1, type=STANDARD), Item(name=Item #4, type=STANDARD)], 
  OTHER=[Item(name=Item #2, type=OTHER)], 
  PREMIUM=[Item(name=Item #3, type=PREMIUM), Item(name=Item #5, type=PREMIUM)]
}

3.2. Transform Values

As the next example, let’s imagine that we would like to get lists of names for each type.

To do so, we can use another version of the groupBy() function:

val groupedByItemsNamesOnly = items.groupBy(
  (Item::type),
  (Item::name)
)
println(groupedByItemsNamesOnly)

Following, let’s validate our example:

{
  STANDARD=[Item #1, Item #4], 
  OTHER=[Item #2], 
  PREMIUM=[Item #3, Item #5]
}

We can clearly see, that this time a Map<ItemType, List<String>> is returned.

Of course, we could map items from the previous example manually to achieve the same result, but this approach helps us to keep our code really neat and concise.

3.3. Split The List

On the contrary, let’s see the example explaining how we should not use the groupBy().

Let’s say that we would like to split our list into two:

  • the first one- containing only PREMIUM items
  • and the second- with others inside

Let’s see how this task can be performed with the groupBy():

val premiumItems = items.groupBy(Item::type)[ItemType.PREMIUM]
val otherItems = 
  items.groupBy(Item::type)
    .filterNot { it.key == ItemType.PREMIUM }
    .values
    .flatten()

println("Premium: $premiumItems")
println("Other types: $otherItems")

As a result, we should get the following output:

Premium: [Item(name=Item #3, type=PREMIUM), Item(name=Item #5, type=PREMIUM)]
Other types: [Item(name=Item #1, type=STANDARD), Item(name=Item #4, type=STANDARD), Item(name=Item #2, type=OTHER)]

Basically, we achieved what we wanted. Nevertheless, this solution is really untidy and other people can really struggle to understand our code.

Of course, it could be simply refactored to such a form:

val premiumTypesFiltered = items.filter { it.type == ItemType.PREMIUM }
val otherTypesFiltered = items.filterNot { it.type == ItemType.PREMIUM }

println("Premium: $premiumTypesFiltered")
println("Other types: $otherTypesFiltered")

Nevertheless, in the next chapter, we will see that we can make this code even better.

4. partition()

With that being said, let’s see what exactly the Kotlin’s partition() function is. According to the documentation:

Splits the original array into pair of lists, where first list contains elements for which predicate yielded true, while second list contains elements for which predicate yielded false.

To put it simply, it requires us to pass a predicate (boolean expression) and as a result, we receive a Pair containing two lists:

  • the first one, with items evaluated to true
  • and the second one- the same, but with false

To better understand it, let’s see its implementation:

public inline fun <T> Iterable<T>.partition(
  predicate: (T) -> Boolean
): Pair<List<T>, List<T>> {
  val first = ArrayList<T>()
  val second = ArrayList<T>()
    for (element in this) {
      if (predicate(element)) {
        first.add(element)
      } else {
        second.add(element)
      }
    }
  return Pair(first, second)
}

We can clearly see, that elements evaluated to true are added to the first ArrayList, whereas those evaluated to false end up in the second. Finally, both of them are combined together to a new Pair instance.

4.1. Simple partition()

With all of that being said, let’s see how does the partition() can help us.

Let’s repeat the splitting example with already possessed knowledge:

val partitioned = items.partition { it.type == ItemType.PREMIUM }

println("Premium: ${partitioned.first}")
println("Other types: ${partitioned.second}")

Nextly, let’s run the above code:

Premium: [Item(name=Item #3, type=PREMIUM), Item(name=Item #5, type=PREMIUM)]
Other types: [Item(name=Item #1, type=STANDARD), Item(name=Item #2, type=OTHER), Item(name=Item #4, type=STANDARD)]

As we can see, thanks to the partition(), we could significantly reduce the amount of code, to achieve the same result.

4.2. partition() and Destructuring Declarations

Nevertheless, let’s see how we can improve our code even more when combining the partition() with destructuring declarations.

To put it simply, a destructuring declaration creates multiple variables at once.

Let’s check the below code to see it in practice:

val (premiumTypes, otherTypes) = items.partition { it.type == ItemType.PREMIUM }

println("Premium: $premiumTypes")
println("Other types: $otherTypes")

Similarly, let’s run the example:

Premium: [Item(name=Item #3, type=PREMIUM), Item(name=Item #5, type=PREMIUM)]
Other types: [Item(name=Item #1, type=STANDARD), Item(name=Item #2, type=OTHER), Item(name=Item #4, type=STANDARD)]

As we can see, thanks to the combination of the partition() and destructuring declarations, we’ve created two new variables: premiumTypes and otherTypes.

Comparing to the previous example, this is the equivalent of:

val premiumTypes = partitioned.first
val otherTypes = partitioned.second

5. Kotlin groupBy() and partition() Summary

And that’s all for this article covering Kotlin’s groupBy() and partition() functions. I am more than happy to share my knowledge with you, and really hope that it will help you become an to become an even better programmer.

As always, you can find the full source code in this GitHub repository.

Finally, if you would like to ask me about anything, just share with me whether you find this kind of posts useful, please let me know in the comments section below, or through the contact form.

 

Previous articles, you might be interested in:

Share this:

Related content

Newsletter
Image presents 3 ebooks with Java, Spring and Kotlin interview questions.

Never miss any important updates from the Kotlin world and get 3 ebooks!

You may opt out any time. Terms of Use and Privacy Policy