When working with collections, programmers often need to check if collection elements meet some condition. Filtering a collection by certain criteria or checking if the collection contains an element with certain properties are among most common tasks, and it is important to do that in the most efficient way.
Fortunately we don't have to apply iterative programming mechanisms – conditionals or loops – to find the solution. We can use functional APIs implemented in Kotlin.
In this topic, we will present techniques to check if the elements of a collection satisfy certain conditions.
Predicate
A predicate is a function that returns true or false based on the input. We will get true if the condition matches the predicate and false otherwise. A single predicate always checks for the same condition; different conditions are checked by different predicates.
A predicate is defined based on a lambda function: (T) -> Boolean. That is, we define a function for a certain type of element and its condition returns true or false. Let's see an example:
// Lambdas as predicates (T) -> Boolean
val isEven: (Int) -> Boolean = { x -> x % 2 == 0 }
val isPalindrome: (String) -> Boolean = { x -> x.reversed() == x }
fun main() {
println(isEven(2)) // true
println(isEven(3)) // false
println(isPalindrome("racecar")) // true
println(isPalindrome("potatoes")) // false
}
Kotlin offers an API that makes use of extension functions and allows you to check the elements of a collection based on predicates. Also, a predicate can be passed as a function reference – it's a very common pattern.
val isEven: (Int) -> Boolean = { x -> x % 2 == 0 }
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// any number is even?
println(numbers.any { x -> x % 2 == 0 }) // true
println(numbers.any { isEven(it) }) // true
println(numbers.any(isEven)) // true
}
These methods available in all basic Kotlin collections (lists, sets, and maps) allow us to check in an efficient way, without iterative programming, whether a predicate is matched by one or more elements, all elements, or none of the elements in a collection.
Any
With a predicate, the any() function will return true if at least one collection element matches the predicate we defined in any(). The function can be also called without a predicate to check for emptiness.
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// any number is even?
println(numbers.any { x -> x % 2 == 0 }) // true
// any number is greater than 100
println(numbers.any { x -> x > 100 }) // false
val palindromes = listOf("racecar", "mom", "dad", "dog")
// any palindrome?
println(palindromes.any { x -> x.reversed() == x }) // true
}None
With a predicate, the none() function will return true if none of the collection elements matches the predicate indicated in none(). Like any(), it can also be called without a predicate to check for emptiness.
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// none of the numbers is even?
println(numbers.none { x -> x % 2 == 0 }) // false
// none of the numbers is greater than 100
println(numbers.none { x -> x > 100 }) // true
val palindromes = listOf("racecar", "mom", "dad", "dog")
// none is a palindrome?
println(palindromes.none { x -> x.reversed() == x }) // false
val palindromes2 = listOf("cat", "dog")
// none is a palindrome?
println(palindromes2.none { x -> x.reversed() == x }) // true
}All
The all() function will only return true if all elements in the collection satisfy the given predicate. A special feature of this function is that it will always return true on an empty collection; this trait is also called vacuous truth in math.
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// all numbers are even?
println(numbers.all { x -> x % 2 == 0 }) // false
// all numbers are less than 100
println(numbers.all { x -> x < 100 }) // true
val palindromes = listOf("racecar", "mom", "dad", "dog")
// all are palindromes?
println(palindromes.all { x -> x.reversed() == x }) // false
val palindromes2 = listOf("racecar", "mom", "dad")
// all are palindromes?
println(palindromes2.all { x -> x.reversed() == x }) // true
val emptyIntList = emptyList<Int>()
// all are even?
println(emptyIntList.all { x -> x % 2 == 0 }) // true
val emptyStringList = emptyList<String>()
// all are palindromes?
println(emptyStringList.all { x -> x.reversed() == x }) // true
}Conclusion
In this topic, we have seen efficient ways to check if the elements of a collection satisfy a condition. Such conditions are defined based on predicates.
We have introduced special functions to test predicates, like any(), none(), and all(). They offer us an elegant and efficient way of checking predicates on the elements of a collection.
Now is the time to check what you have learned. Let's go for it!