article banner

Collection processing in Kotlin: Using indices

This is a chapter from the book Functional Kotlin. You can find it on LeanPub or Amazon. It is also available as a course.

// withIndex implementation from Kotlin stdlib fun <T> Iterable<T>.withIndex(): Iterable<IndexedValue<T>> = IndexingIterable { iterator() } data class IndexedValue<out T>( val index: Int, val value: T )

Sometimes we are not only interested in elements but also in their positions in a collection. Let's say that in one of your collection processing functions you need to depend not only on an element’s value but also on its index in the collection. The generic way is to use the withIndex function, which lazily transforms a list of elements into a list of indexed elements. These elements can be then destructured1 into an index and a value.

fun main() { listOf("A", "B", "C", "D") // List<String> .withIndex() // List<IndexedValue<String>> .filter { (index, value) -> index % 2 == 0 } .map { (index, value) -> "[$index] $value" } .forEach { println(it) } } // [0] A // [2] C

This is a universal iterator function, but many collection processing functions do not need it because they have "indexed" variants. For instance, there are the filterIndexed, mapIndexed, flatMapIndexed, foldIndexed, and scanIndexed functions, which work the same as filter, map, flatMap, fold, and scan, but they also have an index in the first position of their operation.

fun main() { val chars = listOf("A", "B", "C", "D") val filtered = chars .filterIndexed { index, value -> index % 2 == 0 } println(filtered) // [A, C] val mapped = chars .mapIndexed { index, value -> "[$index] $value" } println(mapped) // [[0] A, [1] B, [2] C, [3] D] }

Notice that using withIndex adds the current index to each element, and this index stays the same for all steps, while the indexed function operates on the current index for each step.

fun main() { val chars = listOf("A", "B", "C", "D") val r1 = chars.withIndex() .filter { (index, value) -> index % 2 == 0 } .map { (index, value) -> "[$index] $value" } println(r1) // [[0] A, [2] C] val r2 = chars .filterIndexed { index, value -> index % 2 == 0 } .mapIndexed() { index, value -> "[$index] $value" } println(r2) // [[0] A, [1] C] }

Destructuring is creating multiple variables based on a single value. This concept is explained in the book Kotlin Essentials.