1. Introduction
Sometimes, when we iterate through a List, we may need to keep track of the previous item in the list as we iterate through the elements. For example, if we have a list containing [1, 2, 3, 4, 5], we’ll want to look at one, then one and two, then two and three, then three and four, then four and five. To do this, we look back at the previous element while we iterate. A possible use case could be to obtain a new list that contains all consecutive pairs in the original list, concatenated together. That is [1, 12, 23, 34, 45].
In this tutorial, we’ll explore different approaches to iterate over a collection referencing the previous element in Kotlin. The techniques we’ll discuss demonstrate how to solve the task in the example we just explained.
2. Using a Loop
The most straightforward way involves using a loop to traverse the List:
fun iterateListUsingLoop(list: List<Int>): List<String> {
val newList = mutableListOf<String>()
newList.add(list[0].toString())
for (i in 1 until list.size) {
newList.add("${list[i - 1]}${list[i]}")
}
return newList
}
First, we create a new list of strings for each consecutive concatenated pair. We add the first element to the list since it has nothing before it. Next, we iterate the list elements starting from index position one. Then, we concatenate the current element with the previous element and add the result to the new list.
Let’s test this method for correctness:
@Test
fun `creates new list by adding elements from original list using loop`() {
val list = listOf(1, 2, 3, 4, 5)
val expectedList = listOf("1", "12", "23", "34", "45")
assertEquals(expectedList, iterateListUsingLoop(list))
}
In this test, our helper method correctly creates a new list where each value corresponds to the concatenation of each consecutive pair in the original list.
3. Using the foldIndexed() Method
We can use the foldIndexed() method to reference the previous element from a list. This is useful when we need to perform a calculation on each element of the list while using the previous element as input.
fun iterateListUsingFoldIndexed(list: List<Int>): List<String> {
return list.foldIndexed(mutableListOf()) { i, acc, element ->
if(i==0)
acc.add(element.toString())
if (i > 0) {
acc.add("${list[i - 1]}$element")
}
acc
}
}
The foldIndexed() method takes three arguments: the initial value of an empty accumulator list, an operation to perform on each element of the list, and an optional seed value. The operation performed on each element of the list is defined as a lambda expression that takes three arguments: the index of the current element, the accumulator, and the element itself.
The lambda function checks if the index is greater than zero. If true, we concatenate the current element with the previous element in the list. Then, we add the resulting string to the accumulator. However, if the index is zero, we add the first element to the accumulator. Finally, we return the accumulator.
Also, for the sake of correctness, we’ll test this code:
@Test
fun `creates new list by adding elements from original list using fold method`() {
val list = listOf(1, 2, 3, 4, 5)
val expectedList = listOf("1", "12", "23", "34", "45")
assertEquals(expectedList, iterateListUsingFoldIndexed(list))
}
The elements of this list represent the concatenation of adjacent elements from the original list.
4. Using zipWithNext() Method
We can also use the zipWithNext() method, which is an extension function of List, to achieve our goal:
@Test
fun `creates new list by adding elements from original list using zipWithWext method`() {
val list = listOf("1", "2", "3", "4", "5")
val expectedList = listOf("1", "12", "23", "34", "45")
val result = (list.take(1) + list.zipWithNext { a, b -> "$a$b" })
assertEquals(expectedList, result)
}
First, we add the first list element directly to our final result list since it has no previous element. We use the take() method to do that. This returns a list that we concatenate with the list obtained from zipWithNext().
The zipWithNext() method returns a list of pairs of consecutive elements. It takes a lambda function that defines how to combine the consecutive elements. This lambda function takes two arguments representing a list element and the previous element in the list. So, all we need to do is join these two elements.
5. Using the scan() Method
Lastly, we can also leverage the scan() method, which also accumulates the results of an operation while accessing the previous element:
@Test
fun `creates new list by adding elements from original list using scan method`() {
val list = listOf(1, 2, 3, 4, 5)
val expectedList = listOf("1", "12", "23", "34", "45")
val result = list.drop(1).scan(list.first().toString()) { acc, i -> acc.takeLast(1) + i }
assertEquals(expectedList, result)
}
The scan() method iterates over the elements of the list, applying a given operation sequentially to each element and the accumulator. It starts with the string representation of the first element of the list as the initial accumulator value.
The provided lambda function concatenates the last character of the accumulator with each element of the list, generating a new string at each step. The result is a list of strings representing the intermediate results of the operation.
6. Conclusion
In this article, we explored different approaches to iterate a collection and reference the previous element in Kotlin. We’ve covered several ways to achieve this.
The basic way to solve this is with a for() loop. We’ve also looked at several functional ways, including foldIndexed() and zipWithNext(), to pair consecutive elements. Lastly, the scan() method is a unique approach to building a list of adjacent pairs. It is essential to select the approach that aligns most effectively with the specific requirements of our project.