1. Overview

Lists are one of the most common data structures used by developers in their day-to-day work lives. Sometimes, it might be extremely necessary to efficiently work with lists, especially when they contain a lot of elements.

Modifying a list in-place means updating its values at the same time that we’re looping over the list. This might be useful if the operations we’re performing in the list depend on previous elements of the list.

In this article, we’ll go through several ways that we can use to efficiently update lists in place.

2. Mutable and Immutable Lists

In Kotlin, lists are either mutable or immutable. A mutable list allows us to append and modify elements. On the other hand, we can’t modify an immutable list after we have created it.

Since we want to do an in-place modification, we’re going to use mutable lists in this article.

3. Modify In-Place Using an Iterator

We can iterate over a collection using an iterator. The MutableList interface contains the listIterator() method, which returns a MutableListIterator class.

The MutableListIterator has the typical hasNext() and next() methods to loop over the list but it also contains a set method. This method changes the value of the last element retrieved through the next() method. For example, in the following snippet, we’re iterating over the list and replacing every even element with the number 0:

fun replaceEvenNumbersBy0Iterator(list: MutableList<Int>): MutableList<Int> {
    val iterator = list.listIterator()
    while(iterator.hasNext()) {
        val value = iterator.next()
        if (value % 2 == 0) {
            iterator.set(0)
        }
    }

    return list
}

First, we create a mutable list using the mutableListOf built-in method, and we obtain the iterator by executing the listIterator() method.

Then, we simply verify if the element is even. If it is, we use the set method to modify the last element retrieved.

4. Modify In-Place Using List Setter

Another way of modifying the elements of a list in-place is by setting the value for a specific index. Using a for loop, we iterate from 0 to N – 1, where N is the number of elements in the list.

Then, we simply verify the condition and use the index number to directly set the value in that position:

fun replaceEvenNumbersBy0Direct(list: MutableList<Int>): MutableList<Int> {
    for (i in 0 until list.size) {
        val value = list[i]
        if (value % 2 == 0) {
            list[i] = 0
        }
    }

    return list
}

5. Creating an Extension Method

In Kotlin, it’s possible to extend the capabilities of a class without inheriting or creating a decorator class. This is done by a special declaration of a function called an extension.

We’ll create an extension method on the MutableList class that allows us to apply an operation on every element of the list. The method will receive a function as a parameter, and we’ll apply this function to every element in the list.

Let’s consider an example:

fun <Int> MutableList<Int>.mapInPlace(mutator: (Int) -> (Int)) {
    this.forEachIndexed { i, value ->
        val changedValue = mutator(value)

        this[i] = changedValue
    }
}

We’re creating a new method called mapInPlace for Int elements in the MutableList class that will be available within the context of our application. This way, we avoid modifying the MutableList source code and can include it in selected places.

The method expects a mutator parameter, which is the function that will be applied to every element in the list.

The mutator will verify, based on our conditions, whether the element of the list provided should be changed or not. Let’s see an example of a mutator function:

fun <Int> MutableList<Int>.mapInPlace(mutator: (Int) -> (Int)) {
    this.forEachIndexed { i, value ->
        val changedValue = mutator(value)

        this[i] = changedValue
    }
}

val list = mutableListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.mapInPlace {
    element -> if (element % 2 == 0) 0 else element
}
// [1, 0, 3, 0, 5, 0, 7, 0, 9, 0]
println(list)

As we can see, for every element in the list, we verify if it’s an even number. If so, we return 0, otherwise, we return the element without changes. This returned value is used as the new element for the list.

We can also use the Kotlin keyword it to access the element inside the brackets:

fun <Int> MutableList<Int>.mapInPlace(mutator: (Int) -> (Int)) {
    this.forEachIndexed { i, value ->
        val changedValue = mutator(value)

        this[i] = changedValue
    }
}

val list = mutableListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.mapInPlace {
     if (it % 2 == 0) 0 else it
}
// [1, 0, 3, 0, 5, 0, 7, 0, 9, 0]
println(list)

A possible improvement for the mapInPlace function is to check if the element has really changed before inserting it. We can simply compare the value returned by the mutator function with the current element:

fun <Int> MutableList<Int>.mapInPlace(mutator: (Int) -> (Int)) {
    this.forEachIndexed { i, value ->
        val changedValue = mutator(value)

        if (value != changedValue) {
            this[i] = changedValue
        }
    }
}

This function can be easily generalized to work with a list of any type:

fun <T> MutableList<T>.mapInPlace(mutator: (T) -> (T)) {
    this.forEachIndexed { i, value ->
        val changedValue = mutator(value)

        if (value != changedValue) {
            this[i] = changedValue
        }
    }
}

We can also use an extension method with an Array class:

fun <T> Array<T>.mapInPlace(mutator: (T) -> (T)) {
    this.forEachIndexed { i, value ->
        val changedValue = mutator(value)

        if (value != changedValue) {
            this[i] = changedValue
        }
    }
}

6. Conclusion

In this article, we’ve seen how to update a list as we iterate over it by applying different methods.

As usual, code snippets can be found over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.