1. Overview

When we work with Kotlin, working with collections is a common and essential task. As a fundamental collection type, Lists often require us to iterate through their elements and add new items dynamically.

In this article, we’ll walk through the process of efficiently adding items while iterating through a list in Kotlin.

2. Introduction to the Problem

As usual, let’s understand the problem with an example. Let’s say we have a string list:

["ab", "a", "cd", "c", "xyz"]

Now, we want to walk through the list of elements. Once a string element’s length is greater than 1, we add one element “<- a long one” after it:

["ab", "<- a long one", "a", "cd", "<- a long one", "c", "xyz", "<- a long one"]

Alternatively, depending on the requirement, we may need to add a string element “a long one ->” before those “long elements”:

["a long one ->", "ab", "a", "a long one ->", "cd", "c", "a long one ->", "xyz"]

Moreover, Kotlin offers read-only lists and mutable lists. We encounter several distinct scenarios by considering different insertion position cases alongside the types of lists.

In this tutorial, we’ll discuss all these scenarios and explore different solutions to solve the problem.

3. When the List Is Read-only

Let’s say the given list is a Kotlin read-only list:

val myList = listOf("ab", "a", "cd", "c", "xyz")

As the list is read-only, we cannot really add elements to it. Therefore, we’ll create a mutable list, add the required elements to the right positions, and then return it as a read-only list.

Next, let’s see how to achieve it.

3.1. Using the forEach() Function

A straightforward approach is creating an empty mutable list and then iterating through the given list using the forEach() function. During the iteration, we add elements to the pre-initialized mutable list:

fun byForEach(list: List<String>): List<String> {
    val result = mutableListOf<String>()
    list.forEach {
        result += it
        if (it.length > 1) {
            result += "<- a long one"
        }
    }
    return result.toList()
}

Next, let’s test the byForEach() function with our example input:

assertEquals(
    listOf("ab", "<- a long one", "a", "cd", "<- a long one", "c", "xyz", "<- a long one"),
    byForEach(myList)
)

When we give the test a run, it passes.

3.2. Using the buildList() Function

We can use Kotlin’s buildList() function to save the mutable list declaration:

fun byBuildList(list: List<String>) =
    buildList {
        list.forEach {
            this += it
            if (it.length > 1) {
                this += "<- a long one"
            }
        }
    }

As we can see, the buildList() function constructs a new read-only list by populating a mutable list to accommodate the specified actions, such as add() or the += (plusAssign) operator used in this example.

If we test the buildList() solution using the same input, it produces the expected result:

assertEquals(
    listOf("ab", "<- a long one", "a", "cd", "<- a long one", "c", "xyz", "<- a long one"),
    byBuildList(myList)
)

3.3. Adding the Element Before the Target

So far, our approaches have involved traversing through the list of elements and adding a new element after detecting a “long element.” If the requirement shifts to adding the new element before each “long element,” we can easily adapt current solutions by swapping the if block and the result += it or this += it statements.

Since the adjustments for both the forEach() and buildList() approaches are quite similar, let’s use buildList() as an illustrative example to showcase the modified code:

fun addBeforeByBuildList(list: List<String>) =
    buildList {
        list.forEach {
            if (it.length > 1) {
                this += "a long one ->"
            }
            this += it
        }
    }

Now, if we test the function, we get the expected result:

assertEquals(
    listOf("a long one ->", "ab", "a", "a long one ->", "cd", "c", "a long one ->", "xyz"),
    addBeforeByBuildList(myList)
)

4. When the List Is Mutable

Using the forEach() or buildList() approaches is unnecessary when dealing with a mutable list. This is because we can seamlessly add new elements to the original mutable list during iteration, eliminating the need to create a new list and duplicate all elements.

Next, let’s see how to do it.

4.1. Using ListIterator

ListIterator allows us to modify the list while iterating, for example, adding or removing elements. So, let’s solve the problem using ListIterator:

fun byListIterator(list: MutableList<String>) {
    val it = list.listIterator()
    for (e in it) {
        if (e.length > 1) {
            it.add("<- a long one")
        }
    }
}

As the code above shows, we used ListIterator with a for loop to populate elements in the mutable list. Also, when we detected a “long element”, we insert a new element after it using ListIterator.add().

We can write a test to verify that it works as expected:

val myMutableList = mutableListOf("ab", "a", "cd", "c", "xyz")
byListIterator(myMutableList)
assertEquals(
    listOf("ab", "<- a long one", "a", "cd", "<- a long one", "c", "xyz", "<- a long one"),
    myMutableList
)

4.2. Adding the Element Before Every “Long Element”

We’ve seen that ListIterator allows us to add elements during the iteration. Additionally, with a ListIterator, we can iterate through lists in both forward and backward directions. This is pretty helpful if we need to insert an element before the current entry:

fun addBeforeByListIterator(list: MutableList<String>) {
    val it = list.listIterator()
    for (e in it) {
        if (e.length > 1) {
            it.previous()
            it.add("a long one ->")
            it.next()
        }
    }
}

In the addBeforeByListIterator() function, after identifying a “long element,” our procedure involves utilizing ListIterator.previous() to move one step backward, followed by the addition of the pointer element using add(). Subsequently, we invoke ListIterator.next() to return to the current position.

Finally, let’s test if it solves the problem:

val myMutableList = mutableListOf("ab", "a", "cd", "c", "xyz")
addBeforeByListIterator(myMutableList)
assertEquals(
    listOf("a long one ->", "ab", "a", "a long one ->", "cd", "c", "a long one ->", "xyz"),
    myMutableList
)

5. Conclusion

In this tutorial, we explored various methods for adding elements to a list while iterating through it. Additionally, we looked at both read-only and mutable list scenarios.

As always, the complete source code for the examples is available over on GitHub.