Aggregate Operations in Kotlin

How To Use Aggregate Operations in Kotlin

In this article we will be showing how can we use aggregate operations in Kotlin. Aggregate operations are those operations where we have a collections and based on its elements we return a single value.

Normally this kind of operations are quite useful for statistic purposes. We will take you through the different operations available in Kotlin.

Let’s start!

count

The count method returns the number of elements contained in a collection.

Let’s look at an example by writing a simple unit test:

    @Test
    fun `should return the number of elements in a collection`() {

        val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

        assertThat(numbers.count()).isEqualTo(10)
    }

As you can see, the result is what we should expect, as we have ten elements in the collection.

If you’re wondering how performant the call to count method is, there’s nothing to worry about. Kotlin uses the underlying Java ArrayList and when we call count it’ll just return the stored size variable for the collection.

Java increases the internal size variable every time that an element gets added to the collection. The implementation looks like this:

    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

You can always check the class in the JDK when you are doubtful about their internal implementation.

Let’s move now to a different aggregate operation.

reduce

The reduce method allows us to combine elements in pairs applying a defined aggregate function.

This method can be difficult to understand sometimes, we will try to make it easier in the following test.

    @Test
    fun `should add all elements in a collection`() {

        val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

        val result = numbers.reduce { a, b -> a + b }

        assertThat(result).isEqualTo(55)
    }

You can see how we pass an aggregate function to the reduce method that adds a + b. The way it works is that the element on the left-hand side is the accumulative element after combining the previous two elements.

To clarify this further, let’s go through the example step by step. In our test, a will be 1 initially and b will be the second element in the collection. Therefore the result of the first iteration would be 1 + 2 = 3.

In the second iteration we will have that a = 3, from the previous iteration, and b = 3 as it’s the next element in the collection. The result will be then 3 + 3 = 6.

In the third iteration we will have that a = 6, from the previous iteration’s result again, and b = 4 being the next element in the collection. The result will be then 6 + 4 = 10. This will continue in the same manner until we reach the last element in the collection, I assume you already get the point now.

If you wanted to set an initial value and then apply the elements you can use a different method, fold. Let’s see how it works!

fold

The fold method does exactly the same as reduce, with the only difference being that we now have the ability to define an initial value.

A good use case for this method could be applying an accumulated annual percentage gain after X years to a given amount in pounds. For example:

@Test
fun `should calculate final amount after yearly gains`() {

    val numbers = listOf(1.20.toBigDecimal(), 1.30.toBigDecimal(), 1.10.toBigDecimal())

    val result = numbers.fold(1000.0.toBigDecimal()) { a, b -> a * b }

    assertThat(result).isCloseTo(1716.0.toBigDecimal(), Percentage.withPercentage(0.0001))
}

In this test we create a list containing the gains per year expressed as a percentage. If we have, for example 1.20, this means that the gain on that year was a 20%.

We calculate the final amount after 3 years, passing an initial amount to the fold method ($1,000). The final result after three years is approximately $1,760. In our personal opinion, the name of this function is not very intuitive. The developer won’t be able to easily guess what the purpose of this function is, we can always check the docs in case of doubt anyway.

We have shown previously how to add elements in a collection using reduce, however Kotlin provides a method to do precisely that easily! Let me introduce you to the sum method.

sum

The sum method adds all the elements in a collection, so there’s no need to use reduce in this particular scenario.

Let’s see how the previous test would look like using sum instead.

    @Test
    fun `should add all elements in a collection using sum`() {

        val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

        val result = numbers.sum()

        assertThat(result).isEqualTo(55)
    }

As expected, the result is the same when we use sum compared to reduce, but our code will be more concise.

What if we need the average value in the whole collection? There’s a method for that as well!

average

The average method calculates the average taking into account the value for each element in the collection.

As you could expect, this method returns a Double to be able to show decimal points. We’ll reuse the previous test to adapt it for this use case:

    @Test
    fun `should return average value for collection`() {

        val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

        val result = numbers.average()

        assertThat(result).isEqualTo(5.5)
    }

The result is what we’d expect. If the result of sum was 55, considering that we have 10 elements in our collection, it’s expected that the average value is 5.5!

max

As you could guess from its name, max method returns the maximum element contained in a collection of numbers.

Let’s look at a simple example.

    @Test
    fun `should return max value in a collection`() {

        val numbers = listOf(120, 119, 1, -1, 13, 99, 100)

        val result = numbers.max()

        assertThat(result).isEqualTo(120)
    }

As expected, we get back 120 as the maximum value. What if we don’t have a list of integers? What if we have a list of users instead? We can use maxBy!

maxBy

The maxBy method allows us to specify the criteria to be used to determine what’s the highest element in a collection.

You will be able to understand it easily with this example:

    @Test
    fun `should return the oldest user in a collection of users`() {

        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 oldestUser = users.maxBy { user -> user.age }

        assertThat(oldestUser).isEqualTo(User("Jessica Fletcher", 63, Gender.FEMALE))
    }

In this case we tell the compiler to use the age field to determine what the maximum is. Simple, right? What if we need the minimum? We also have min method.

min

The min method returns the minimum element contained in a collection of numbers.

Look at this simple example:

    @Test
    fun `should return min value in a collection`() {

        val numbers = listOf(120, 119, 1, -1, 13, 99, 100)

        val result = numbers.min()

        assertThat(result).isEqualTo(-1)
    }

As expected, the result is -1 as it’s the lowest value among the values in the collection.

As we’ve seen for the maximum element in a collection of objects, we also have a minBy to be able to specify the criteria to define the minimum.

minBy

The minBy method does exactly the same as min, with the difference that it allows specifying a criteria to be able to determine the minimum among a collection of objects.

If we apply this to our collection of users to determine the youngest user, we have the following:

    @Test
    fun `should return the youngest user in a collection of users`() {

        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 oldestUser = users.minBy { user -> user.age }

        assertThat(oldestUser).isEqualTo(User("Jack Johnson", 27, Gender.MALE))
    }

As simple as that, we were able to tell the compiler that we’d use the age field to determine the minimum element.

We could continue looking at different aggregate operations in Kotlin for a long time or the different variations of the methods already described in this article, but for simplicity we are going to leave it here!

Conclusion

We’ve seen different ways of applying aggregate operations in this article, these methods are quite handy when we need to get any kind of statistics from our data.

If you are interested in reading more articles about Kotlin, you can find many of them here.

That’s all from us today, we really hope you’ve enjoyed reading this article together! We’re looking forward to seeing you again very soon with us.

Please follow us if you like our articles and want to be notified when new content gets published! Thanks in advance!