Working with collections, we make use of grouped sets of elements or items, which is essential in a variety of tasks. In previous topics, you have learned to select collection elements, order them, retrieve some of them, and even group or transform them according to your needs.
In this topic, we will focus on single elements and explore different methods Kotlin offers for working with them.
Retrieve by position
Collections, such as lists or sets, are accessed via indices, which start at 0 (the first element) and go up to size-1 (the last element of the collection).
To get the element at a specific index position, you can use elementAt(). We can also fetch an element in an equivalent way with the get() function.
However, in the Kotlin world, things are easier if we use the indexed access operator [], which is equivalent to the get() function on lists.
All of them will throw an IndexOutOfBoundsException if the index is not within the indicated limits (0.. size-1).
fun main() {
val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val numberSet = setOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// elementAt() returns the element at the specified index
println(numberList.elementAt(3)) // 4
println(numberSet.elementAt(3)) // 4
// get() returns the element at the specified index
println(numberList.get(3)) // 4
// println(numberSet.get(3)) // error: 'get' is not a member of 'Set'
// [] returns the element at the specified index
println(numberList[3]) // 4
// println(numberSet[3]) // error: 'get' is not a member of 'Set'
// be careful with the index – it can throw an exception if the index is out of bounds
// println(numberList[30]) // error: Index 30 out of bounds for length 10
// println(numberSet.elementAt(30)) // error: Index 30 out of bounds for length 10
}Some of the most common operations include getting the first (index = 0) and the last (index = size-1) item in a collection. To avoid mistakes with the indices, we can use first() and last().
fun main() {
val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val numberSet = setOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// first() returns the first element
println(numberList.first()) // 1
println(numberSet.first()) // 1
// last() returns the last element
println(numberList.last()) // 10
println(numberSet.last()) // 10
}In order not to have problems with exceptions when trying to get an invalid index element, we can make use of the following safe calls:
elementAtOrNull(): it will return null when the index position is out of the collection bounds.elementAtOrElse(): it takes a lambda function that returns the result of the lambda if the index position is out of the collection bounds.getOrNull(): it is the equivalent toelementAtOrNull()for a list.getOrElse(): it is the equivalent toelementAtOrElse()for a list.firstOrNull(): it is used to get the first element or null if the collection is empty.lastOrNull(): it is used to get the last element or null if the collection is empty.
fun main() {
val emptyList = listOf<Int>()
val emptySet = setOf<Int>()
// firstOrNull() returns the first element or null if the list is empty
println(emptyList.firstOrNull()) // null
println(emptySet.firstOrNull()) // null
// lastOrNull() returns the last element or null if the list is empty
println(emptyList.lastOrNull()) // null
println(emptySet.lastOrNull()) // null
// elementAtOrNull() returns the element at the specified index or null if the index is out of bounds
println(numberList.elementAtOrNull(30)) // null
println(numberSet.elementAtOrNull(30)) // null
// getOrNull() returns the element at the specified index or null if the index is out of bounds
println(numberList.getOrNull(30)) // null
// println(numberSet.getOrNull(30)) // error: 'getOrNull' is not a member of 'Set'
// elementAtOrElse() returns the element at the specified index
// or the result of the given function if the index is out of bounds
println(numberList.elementAtOrElse(30) { "element not found" }) // element not found
println(numberSet.elementAtOrElse(30) { "element not found" }) // element not found
// getOrElse() returns the element at the specified index
// or the result of the given function if the index is out of bounds
println(numberList.getOrElse(30) { "element not found" }) // element not found
// println(numberSet.getOrElse(30) { "element not found" }) // error: 'getOrElse' is not a member of 'Set'
}Retrieve by condition
Some functions allow you to get elements based on a predicate: first() and last(). You will only get the element if the predicate is true. To avoid an exception, you can use the secure calls: firstOrNull() and lastOrNull().
For the sake of ease, you can use these handy aliases:
find(): it is used instead offirstOrNull()and returns the first collection element according to a predicate or null if it does not exist.findLast(): it is used instead oflastOrNull()and returns the last collection element according to a predicate or null if it does not exist.
fun main() {
val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val numberSet = setOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// first() with a lambda predicate
println(numberList.first { it > 5 }) // 6
println(numberSet.first { it > 5 }) // 6
// last() with a lambda predicate
println(numberList.last { it > 5 }) // 10
println(numberSet.last { it > 5 }) // 10
// be careful with the predicate – it can throw an exception if the predicate is not satisfied
// println(numberList.first { it > 50 }) // error: NoSuchElementException
// println(numberSet.first { it > 50 }) // error: NoSuchElementException
// println(numberList.last { it > 50 }) // error: NoSuchElementException
// println(numberSet.last { it > 50 }) // error: NoSuchElementException
// firstOrNull() with a lambda predicate to return null if the predicate is not satisfied
println(emptyList.firstOrNull { it > 50 }) // null
println(emptySet.firstOrNull { it > 50 }) // null
// lastOrNull() with a lambda predicate to return null if the predicate is not satisfied
println(emptyList.lastOrNull { it > 50 }) // null
println(emptySet.lastOrNull { it > 50 }) // null
// using find() with a lambda predicate
println(numberList.find { it > 5 }) // 6
println(numberSet.find { it > 5 }) // 6
// using findLast() with a lambda predicate
println(numberList.findLast { it > 5 }) // 10
println(numberSet.findLast { it > 5 }) // 10
// using find() and findLast() with a lambda to return null if the predicate is not satisfied
println(emptyList.findLast { it > 50 }) // null
println(emptySet.findLast { it > 50 }) // null
}Retrieve with selector
Sometimes, it is helpful to use a selector to map the collection before getting an element. The firstNotNullOf() method performs the following actions: maps the collection with a selector function and returns the first non-null value in the result. However, it throws NoSuchElementException if there is no non-null element. To avoid that, you can use firstNotNullOfOrNull().
fun main() {
val listOfNames = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")
// Mapping and returning a list of values
println(
listOfNames.firstNotNullOf { item -> item.uppercase().takeIf { it.length >= 4 } }
) //JOHN
/*
println(
listOfNames.firstNotNullOf { item -> item.uppercase().takeIf { it.length >= 10 } }
) // Exception
*/
println(
listOfNames.firstNotNullOfOrNull { item -> item.uppercase().takeIf { it.length >= 4 } }
) //JOHN
println(
listOfNames.firstNotNullOfOrNull { item -> item.uppercase().takeIf { it.length >= 10 } }
) // null
}Random element
Oftentimes, what you need is to obtain a random value from a collection. To do that, we can use the random() function. Remember, though, that this function throws NoSuchElementException if the array is empty. To avoid that, you can use the safer call randomOrNull(), which returns null if the collection is empty.
fun main() {
val listOfNames = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")
// returning a random element
println(listOfNames.random()) // Peter
println(listOfNames.randomOrNull()) // Jane
val emptyListNames = listOf<String>()
// println(emptyListNames.random()) // Exception
println(emptyListNames.randomOrNull()) // null
}Check if an element exists
One of the crucial and daily tasks you will face when working with collections is to check if an element or a set of elements exists.
To do that, you can use the contains() method – this method returns true if the element is found in the collection. You can also do it in a more idiomatic way in Kotlin with the operator in. To check if a collection contains multiple elements, you can make use of the method containsAll().
Also, you can check if the collection has any elements with isEmpty() (it returns true if the collection is empty) or isNotEmpty() (it returns true if the collection is not empty).
fun main() {
val listOfNames = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")
val emptyListNames = listOf<String>()
// contains() returns true if the element is present in the list
println(listOfNames.contains("John")) // true
println(listOfNames.contains("john")) // false
// the in operator can also be used to check if an element is present in the list
println("John" in listOfNames) // true
println("john" in listOfNames) // false
// containsAll() returns true if all the elements are present in the list
println(listOfNames.containsAll(listOf("John", "Jane"))) // true
println(listOfNames.containsAll(listOf("John", "Jane", "john"))) // false
// list is empty
println(listOfNames.isEmpty()) // false
println(emptyListNames.isEmpty()) // true
// list is not empty
println(listOfNames.isNotEmpty()) // true
println(emptyListNames.isNotEmpty()) // false
}Conclusion
In this topic, you have explored different ways of getting elements from a collection: by indicating the position, using conditions, with a selector, or randomly. Also, you have learned how to check if an element or several elements are contained in a collection.
Now, you are going to put it all into practice. Are you ready for the challenge?