article banner (priority)

Collection processing in Kotlin: Finding, counting, and checking conditions

This is a chapter from the book Functional Kotlin. You can find it on LeanPub.

If you need to get the first element of a collection, use the first function. To get the last one, use the last function. To find an element at a concrete index, use the get function, which is also an operator and can be replaced with box brackets. You can also destructure a list into elements, starting at the first position.

fun main() { val c = ('a'..'z').toList() println(c.first()) // a println(c.last()) // z println(c.get(3)) // d println(c[3]) // d val (c1, c2, c3) = c println(c1) // a println(c2) // b println(c3) // c }

A problem arises when a collection is empty. In such a case, all the functions above throw an exception (NoSuchElementException or IndexOutOfBoundsException). To prevent this, use the variants of these functions with the "OrNull" suffix.

fun main() { val c = listOf<Char>() println(c.firstOrNull()) // null println(c.lastOrNull()) // null println(c.getOrNull(3)) // null }

Finding an element

Out of a whole collection of elements, we often want to find a single one that fulfills a predicate. It might be a user with a certain id, or a configuration with a concrete name. The most basic method of finding an element in a collection is find.

fun getUser(id: String): User? = users.find { it.id == id } fun findConfiguration(name: String): Configuration? = configurations.find { it.name == name }

find is just an alias for firstOrNull. They both return the first element that fulfills the predicate, or null if no such element is found.

fun main() { val names = listOf("Cookie", "Figa") println(names.find { it.first() == 'A' }) // null println(names.firstOrNull { it.first() == 'A' }) // null println(names.find { it.first() == 'C' }) // Cookie println(names.firstOrNull { it.first() == 'C' }) // Cookie println(listOf(1, 2, 6, 11).find { it in 2..10 }) // 2 }

If you prefer to start searching from the end, you can use findLast or lastOrNull.

fun main() { val names = listOf("C1", "C2") println(names.find { it.first() == 'C' }) // C1 println(names.firstOrNull { it.first() == 'C' }) // C1 println(names.findLast { it.first() == 'C' }) // C2 println(names.lastOrNull { it.first() == 'C' }) // C2 }

Counting: count

Counting the number of elements in a list is easy as we can always use the size property. However, some collections that implement the Iterable interface might require iterating over elements to count how many elements they have. The universal method of counting the number of elements in a collection is count.

fun main() { val range = (1..100 step 3) println(range.count()) // 34 }

We can also add a predicate to count in order to count the number of elements that satisfy this predicate. For instance, we could count the number of users with a premium account, or the number of students that qualify for an internship.

val premiumUsersCount = users .count { it.hasPremium } val qualifiedNum = students .count { qualifiesForInternship(it) }

The count method returns the number of elements for which the predicate returned true.

fun main() { val range = (1..100 step 3) println(range.count { it % 5 == 0 }) // 7 }

any, all and none

To check if a condition is true for all, any, or none of the elements in a collection, we use respectively all, any and none. They all return a Boolean. Let's see some examples.

data class Person( val name: String, val age: Int, val male: Boolean ) fun main() { val people = listOf( Person("Alice", 31, false), Person("Bob", 29, true), Person("Carol", 31, true) ) fun isAdult(p: Person) = p.age > 18 fun isChild(p: Person) = p.age < 18 fun isMale(p: Person) = p.male fun isFemale(p: Person) = !p.male // Is there an adult? println(people.any(::isAdult)) // true // Are they all adults? println(people.all(::isAdult)) // true // Is none of them an adult? println(people.none(::isAdult)) // false // Is there any child? println(people.any(::isChild)) // false // Are they all children? println(people.all(::isChild)) // false // Are none of them children? println(people.none(::isChild)) // true // Are there any males? println(people.any { isMale(it) }) // true // Are they all males? println(people.all { isMale(it) }) // false // Is none of them a male? println(people.none { isMale(it) }) // false // Are there any females? println(people.any { isFemale(it) }) // true // Are they all females? println(people.all { isFemale(it) }) // false // Is none of them a female? println(people.none { isFemale(it) }) // false }

Beware: Developers often confuse the methods for finding elements, like find or last, with methods for checking a condition on elements, like any.

For empty collections, the predicate is never called. any returns false, while all and none return true. These values come from the mathematical definitions of these functions1.

fun main() { val emptyList = emptyList<String>() println(emptyList.any { error("Ignored") }) // false println(emptyList.all { error("Ignored") }) // true println(emptyList.none { error("Ignored") }) // true }
1:

To learn more about this, search under the term "vacuous truth".