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!