9 minutes read

In previous topics, we have learned to order collections or filter their elements based on predicates.

It is time to learn to obtain values based on the content of a collection, for example, the maximum value, the average value, an object with the maximum value of a property, etc. We will see that Kotlin has handy methods that make this task an easy job.

Basic aggregate function

When we work with collections, we have a series of methods to obtain values based on the collection content and get additional information about the content. These methods are known as "aggregation methods":

  • minOrNull() returns the smallest element of a collection or null on an empty collection.

  • maxOrNull() returns the largest element of a collection or null on an empty collection.

  • average() returns the average value of the elements in a collection with number values.

  • sum() returns the sum of all elements in a collection with number values.

  • count() returns the number of elements in a collection.

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

    // Min and Max
    println(numbers.minOrNull()) // 1
    println(numbers.maxOrNull()) // 10
    
    // Average
    println(numbers.average()) // 5.5
   
    // Sum
    println(numbers.sum()) // 55
    
    // Count
    println(numbers.count()) // 10
}

Aggregate with selector function

Sometimes, we need to specify which value we want to obtain with an aggregate method. In Kotlin, we have special methods to perform these tasks using a selector function. They are:

  • minByOrNull() with a selector function, returns the element with the smallest value.

  • maxByOrNull() with a selector function, returns the element with the largest value.

  • minOfOrNull() with a selector function, returns the smallest return value of the selector itself.

  • maxOfOrNull() with a selector function, returns the largest return value of the selector itself.

  • sumOf() with a selector function, returns the sum of its application to all collection elements.

minByOrNull()/maxByOrNull() and minOfOrNull()/maxOfOrNull() return null on empty collections. Also, you can use predicates to find the max or min elements.

fun main() {
    val words = listOf("anne", "michael", "caroline", "dimitry", "emilio")

    // Count
    println(words.count()) // 5
    
    // maxByOrNull/minByOrNull
    println(words.maxByOrNull { it.length }) // caroline
    println(words.minByOrNull { it.length }) // anne
    
    //maxOfOrNull/minOfOrNull
    println(words.maxOfOrNull { it.length }) // 8
    println(words.minOfOrNull { it.length }) // 4
    
    // sumOf
    println(words.sumOf { it.length }) // 32

    // using predicates, length > 5
    println(words.count { it.length > 5 }) // 4
    println(words.maxByOrNull { it.length > 5 }) // michael
    println(words.minByOrNull { it.length > 5 }) // anne

    // emptyList
    val emptyWordsList = emptyList<String>()
    println(emptyWordsList.count()) // 0
    println(emptyWordsList.maxByOrNull { it.length }) // null
    println(emptyWordsList.minByOrNull { it.length }) // null
    println(emptyWordsList.maxOfOrNull { it.length }) // null
    println(emptyWordsList.minOfOrNull { it.length }) // null
}

It is important to note the difference between maxOfOrNull/minOfOrNull and maxByOrNull/minByOrNull. maxOfOrNull/minOfOrNull return the largest/smallest value among all values produced by the selector function applied to each element in the collection or null if the collection is empty. In the example, the selector is based on the word length. They return 8 (max length) and 4 (min length). Thus, they return the maximum or minimum of the word lengths.

Meanwhile, maxByOrNull/minByOrNull return the first element yielding the largest/smallest value of the given function or null if there are no elements. Thus, they will return the first word that has the maximum or minimum length. They return "caroline" (the first word with the max length 8) and "anne" (the first word with the min length 4). Also, we can use a lambda predicate like it.length > 5 to obtain the first max or min word based on its length because it is the first element that produces the largest (michael) /smallest (anne) value of the given function (it.length > 5).

Aggregate with comparator

We can perform the same task as above using a custom comparator with a lambda and the following methods:

  • minWithOrNull() returns the smallest element according to the comparator.

  • maxWithOrNull() returns the largest element according to the comparator.

  • minOfWithOrNull() returns the smallest selector return value according to the comparator.

  • maxOfWithOrNull() returns the largest selector return value according to the comparator.

  • count() returns the number of items according to the comparator.

These methods return null on empty collections.

fun main() {
    val words = listOf("anne", "michael", "caroline", "dimitry", "emilio")

    // MaxWithOrNull/MinWithOrNull
    println(words.maxWithOrNull(compareBy { it.length })) // caroline
    println(words.minWithOrNull(compareBy { it.length })) // anne

    // maxOfWithOrNull/minOfWithOrNull
    println(words.maxOfWithOrNull(naturalOrder()) { it.length }) // 8
    println(words.minOfWithOrNull(naturalOrder()) { it.length }) // 4
    
    // count
    println(words.count { it.first() == 'a' }) // 1
}

Finally, you can combine your custom ordering method with the reversed() function to obtain the inverse form of the defined order.

fun main() {
    val words = listOf("peter", "anne", "michael", "caroline")

    // ordering by length reversed
    println(words.sortedWith(compareBy { it.length }).reversed()) // [caroline, michael, peter, anne]
    
    // ordering by middle letter reversed
    println(words.sortedWith(compareBy { it[it.length / 2] }).reversed()) // [peter, anne, caroline, michael]
}

Conclusion

In this topic, we have discussed different techniques to obtain the values of a collection using aggregate methods: to find the max, min, or average value, or find the object that has the max value of a certain property.

It is time to solve some problems to check what you have learned about aggregate methods. Let's go!

58 learners liked this piece of theory. 9 didn't like it. What about you?
Report a typo