In previous topics, you have learned and practiced many of the common operations on collections: sorting, filtering, and getting items.
In this topic, we will learn how to obtain summary information based on the content of the collection. We will see that Kotlin has various handy methods that make this task an easy job.
Counting elements
One of the main tasks when working with collections is counting the elements. Obviously, we can make use of size, but an interesting fact is that we can also use a predicate to count the number of matching collection elements.
To perform this action, we can apply the count() method. This methods returns the number of collection elements or the number of the elements in a collection that match the given predicate: for example, we can count the number of even elements or the number of words whose length is greater than 5 or which start with "J".
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val names = listOf("Pablo", "John", "Jane", "Mary", "Peter")
// number of elements
println(numbers.size) // 10
println(names.size) // 5
// number of elements
println(numbers.count()) // 10
println(names.count()) // 5
// number of elements that match the condition
println(numbers.count { it % 2 == 0 }) // 5
println(names.count { it.startsWith("J") }) // 2
}Distinct elements
Oftentimes, you do not know if your collection contains elements that appear more than once. For this reason, it is very helpful to know the unique elements in a collection. The distinct() methods returns a list with only the distinct elements in the given collection. Also, distinctBy() returns a collection containing only the elements with distinct keys returned by the given selector function.
fun main() {
val numberWithRepeated = listOf(1, 2, 3, 4, 5, 4, 4, 3, 1)
val namesWithRepeated = listOf("Pablo", "John", "Jane", "John", "Jane")
// distinct elements
println(numberWithRepeated.distinct()) // [1, 2, 3, 4, 5]
println(namesWithRepeated.distinct()) // [Pablo, John, Jane]
// distinct by a condition
println(numberWithRepeated.distinctBy { it % 2 }) // [1, 2]
// -> 1 % 2 = 1 -> 2 % 2 = 0 -> 3 % 2 = 1 -> 4 % 2 = 0 -> 5 % 2 = 1
println(namesWithRepeated.distinctBy { it.first() }) // [Pablo, John]
// -> "Pablo".first() = 'P' -> "John".first() = 'J' -> "Jane".first() = 'J'
}Sum elements
Sometimes, your collection contains elements that can be added and you may want to know their total value. sum() returns the sum of all collection items. You can also use sumOf(), which returns the sum of all the values produced by the selector function applied to each element in the collection. Remember, though: these methods only work with numbers, and if the collection is of a different data type, they will throw an exception.
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val names = listOf("Pablo", "John", "Jane", "Mary", "Peter")
// sum of elements
println(numbers.sum()) // 55
// sum of elements that match the condition
println(numbers.sumOf { it * 2 }) // 110
println(names.sumOf { it.length }) // 22
println(numbers.sumOf { if (it % 2 == 0) it else 0 }) // 30
println(names.sumOf { if (it.startsWith("J")) it.length else 0 }) // 8
}Average
In certain cases, it is very useful to have the average value of the collection elements. average() will return the average value of the items in your collection. Remember, though, that this method only works with numbers, and if the collection is of a different data type, it will throw an exception.
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val names = listOf("Pablo", "John", "Jane", "Mary", "Peter")
// average of elements
println(numbers.average()) // 5.5
// average of elements that match the condition
println(numbers.filter { it % 2 == 0 }
.average()
) // 6.0
// average length of names
println(names.map { it.length }
.average()
) // 4.4
// average length of names that start with J
println(names.filter { it.startsWith("J") }
.map { it.length }
.average()
) // 4.0
}Max and Min
Quite often, you may want to find the maximum and minimum elements in a collection. For that, we have the following methods:
minOrNull()returns the smallest element or null if there are no elements.maxOrNull()returns the largest element or null if there are no elements.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.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.
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val words = listOf("anne", "michael", "caroline", "dimitry", "emilio")
//maxOrNull/minOrNull()
println(numbers.maxOrNull()) //10
println(numbers.minOrNull()) //1
// 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
// maxByOrNull/minByOrNull
println(words.count { it.length > 5 }) // 4
println(words.maxByOrNull { it.length > 5 }) // michael
println(words.minByOrNull { it.length > 5 }) // anne
// 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
}All the methods that end with OrNull return null on empty collections, when they cannot perform the operation.
It is important to note the difference between maxOfOrNull/minOfOrNull and maxByOrNull/minByOrNull. maxOfOrNull/minOfOrNull return the largest/smallest value among all the values produced by the selector function applied to each collection element or null if the collection is empty. In the above 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 word length.
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) or smallest (anne) value of the given function (it.length > 5).
Conclusion
In this topic, you have learned different methods to obtain summary information based on the existing collection values. Using these methods, you will be able to find the number of elements; different elements; as well as mean, maximum, and minimum elements using lambda functions, selectors, and predicates, which will allow you to efficiently perform your tasks.
It is time to solve some problems to check what you have learned so far. Let's go!