Group elements in Kotlin

Discover How To Group Elements in Kotlin

In this short article we will be looking at how can we group elements in Kotlin or to say it in a different way, how to convert a flat collection to a Map in Kotlin. There are different operations over collections that you will normally find on a day-to-day job as a software developer, this kind of transformation is normally used to categorise data in different groups. Learning how to group elements in Kotlin will be very useful in those jobs where categorising data is needed.

Grouping elements is a way to categorise elements by associating them to a given key of a map. Let’s see how can we achieve that.

groupBy

The groupBy method allows us to group the elements in a collection using one field in an element or a combination of them.

Looking at a simple unit test you will be able to understand how it works easily. For this test we have created the following User class.

data class User(val name: String, val age: Int, val gender: Gender)

enum class Gender {
    FEMALE,
    MALE
}

Using this User object to create a collection, our test will look like this:

    @Test
    fun `should group users by gender`() {

        val users: List<User> = listOf(
            User("John Smith", 39, Gender.MALE),
            User("Jennifer Lawrence", 35, Gender.FEMALE),
            User("Jack Johnson", 27, Gender.MALE),
            User("Robert Downey", 51, Gender.MALE),
            User("Jessica Fletcher", 63, Gender.FEMALE),
        )

        val usersByGender = users.groupBy { user -> user.gender }

        assertThat(usersByGender).isEqualTo(mutableMapOf<Gender, List<User>>(
            Gender.FEMALE to listOf(
                User("Jennifer Lawrence", 35, Gender.FEMALE),
                User("Jessica Fletcher", 63, Gender.FEMALE)
            ),
            Gender.MALE to listOf(
                User("John Smith", 39, Gender.MALE),
                User("Jack Johnson", 27, Gender.MALE),
                User("Robert Downey", 51, Gender.MALE)
            )
        ))
    }

We have created an initial list containing five users and we use one of its fields, the gender, to group users by gender. The resulting collection is a Kotlin Map whose key is of type Gender and its value is a list of users.

That looks easy, right? What if we needed to count the numbers of users for each gender? This is where groupingBy method comes quite handy. It’s a different way to group elements in Kotlin but with a bit more of flexibility, let’s see how it works!

Group Elements in Kotlin
Photo by Ulvi Safari on Unsplash

groupingBy

The method groupingBy returns an object which implements the Grouping interface. The Grouping interface provides a series of methods that allow applying functions to each group at one time. We’ll be looking at the different options that this interface provides and how can we take advantage of them in one of our next articles.

For the time being, let’s look at an example using our previous test, with the difference that this time we’ll be counting the number of users for each gender.

@Test
fun `should count users by gender`() {

    val users: List<User> = listOf(
        User("John Smith", 39, Gender.MALE),
        User("Jennifer Lawrence", 35, Gender.FEMALE),
        User("Jack Johnson", 27, Gender.MALE),
        User("Robert Downey", 51, Gender.MALE),
        User("Jessica Fletcher", 63, Gender.FEMALE),
    )

    val usersByGender = users.groupingBy { user -> user.gender }.eachCount()

    assertThat(usersByGender).isEqualTo(mutableMapOf<Gender, Int>(
        Gender.FEMALE to 2,
        Gender.MALE to 3
    ))
}

As you can see, we have modified our test to return the count for each group by combining groupingBy and eachCount methods. GroupingBy is very powerful for aggregate operations, we’ll see more about that in our article “How to use aggregate operations in Kotlin”.

groupByTo

In the same way that we learned how to use filterTo in our article “How to filter collections in Kotlin“, we can also find groupByTo method to be able to group elements in Kotlin and append the result to an existing map.

As we just said, this method allows us to append the result to an existing map, this map has to be a mutable map then. Let’s see how can we use it.

    @Test
    fun `should group users by gender into an existing map`() {

        val existing = mutableMapOf(
            Gender.FEMALE to mutableListOf(
                User("Jennifer Lawrence", 35, Gender.FEMALE),
            ),
            Gender.MALE to mutableListOf(
                User("Jack Johnson", 27, Gender.MALE),
                User("Robert Downey", 51, Gender.MALE),
            )
        )

        val users: List<User> = listOf(
            User("Jennifer Aniston", 47, Gender.FEMALE),
            User("Alfred Hitchcock", 90, Gender.MALE),
            User("James Rodriguez", 34, Gender.MALE),
            User("Jessica Fletcher", 63, Gender.FEMALE),
        )

        val usersByGender = users.groupByTo(existing) { user -> user.gender }

        assertThat(usersByGender).isEqualTo(mutableMapOf<Gender, List<User>>(
            Gender.FEMALE to listOf(
                User("Jennifer Lawrence", 35, Gender.FEMALE),
                User("Jennifer Aniston", 47, Gender.FEMALE),
                User("Jessica Fletcher", 63, Gender.FEMALE)
            ),
            Gender.MALE to listOf(
                User("Jack Johnson", 27, Gender.MALE),
                User("Robert Downey", 51, Gender.MALE),
                User("Alfred Hitchcock", 90, Gender.MALE),
                User("James Rodriguez", 34, Gender.MALE)
            )
        ))
    }

You can see how we have an existing map with some users grouped by gender and then we group another list of users to concatenate the results to the existing map.

The groupByTo method has another form that also accepts an additional argument to transform the collection elements. Let’s see how!

groupByTo (with transform)

As we just said, this groupByTo method allows us to transform an object in the same way that map does in any Kotlin collection by providing an additional argument. This means that by using this method we can group elements in Kotlin and, on top of that, apply a transformation to each element!

We’re going to reuse our test example once more with one simple tweak. We have now an existing map with users whose name is in uppercase but the second list has its users’ names not capitalised. Therefore we’d like to homogenise the format in both collections by transforming the name to uppercase.

Let’s see how that would look like.

    @Test
    fun `should group users by gender into an existing map changing name to uppercase`() {

        val existing = mutableMapOf(
            Gender.FEMALE to mutableListOf(
                User("JENNIFER LAWRENCE", 35, Gender.FEMALE),
            ),
            Gender.MALE to mutableListOf(
                User("JACK JOHNSON", 27, Gender.MALE),
                User("ROBERT DOWNEY", 51, Gender.MALE),
            )
        )

        val users: List<User> = listOf(
            User("Jennifer Aniston", 47, Gender.FEMALE),
            User("Alfred Hitchcock", 90, Gender.MALE),
            User("James Rodriguez", 34, Gender.MALE),
            User("Jessica Fletcher", 63, Gender.FEMALE),
        )

        val usersByGender = users.groupByTo(existing, { user -> user.gender }) {
                user -> User(user.name.uppercase(), user.age, user.gender)
        }

        assertThat(usersByGender).isEqualTo(mutableMapOf<Gender, List<User>>(
            Gender.FEMALE to listOf(
                User("JENNIFER LAWRENCE", 35, Gender.FEMALE),
                User("JENNIFER ANISTON", 47, Gender.FEMALE),
                User("JESSICA FLETCHER", 63, Gender.FEMALE)
            ),
            Gender.MALE to listOf(
                User("JACK JOHNSON", 27, Gender.MALE),
                User("ROBERT DOWNEY", 51, Gender.MALE),
                User("ALFRED HITCHCOCK", 90, Gender.MALE),
                User("JAMES RODRIGUEZ", 34, Gender.MALE)
            )
        ))
    }

In the final results you can see that all the users have their names capitalised. As this method accepts two function arguments, we have to move the first function inside of the enclosing parenthesis. In case you don’t know it already, Kotlin allows specifying the last function argument within curly brackets and we still define the transform function within the curly brackets.

If you are interested in knowing about Kotlin in more detail, we recommend the following books for your own reading:

Conclusion

We’ve seen how to group elements in Kotlin by using groupBy, groupByTo and groupingBy methods. These methods allow us to do different kind of aggregation operations that can be very useful in our day-to-day work.

If you’re interested in reading more Kotlin articles, you can find more of our Kotlin articles here.

That’s all from us today! We hope you’ve found this article useful and hopefully learned something new.

Please follow us if you’re interested in our articles. We hope to see you with us again very soon!