Sunday, 26 February 2023

Kotlin MutableIterable

This post is a sort of kotlin follow up to this post from last year. Kotlin follows the common iterable-iterator nomenclature (the only platform that uses a different naming, IEnumerable-IEnumerator is .Net...), and same as in Python, Iterators are also Iterables. They are iterable because there is an extension function for the Iterator interface that returns the iterator itself. This means that the Iterator interface does not inherit from the Iterable interface. I wonder if there's any reason for implementing it this way rather than as a default Interface method. From here:

Returns the given iterator itself. This allows to use an instance of iterator in a for loop.

Kotlin adds something new (to me) to the Iterable-Iterator pair, it features also a MutableIterable-MutableIterator couple. A MutableIterator has a remove method that removes from the underlying collection the last element returned by the iterator. You can see a usage example here


val numbers = mutableListOf(1,2,3,4,5,6)
val numberIterator = numbers.iterator()
while (numberIterator.hasNext()) {
    val integer = numberIterator.next()
    if (integer < 3) {
        numberIterator.remove()
    }
}

Including a remove-delete capabiliby in the iteration logic is not common. Neither JavaScript, nor Python nor .Net do this. I wrote an article about iteration and removal one decade ago and reading it again now has helped me to refresh ideas. I had forgotten that as I explain there Java iterators have a remove method. The Kotlin approach of having 2 different pairs, Iterable-Iterator, MutableIterable-MutableIterator seems much more correct. As Kotlin has had mutable and immutable collections since the beginning (I think) the need for this distinction had to seem evident to the smart guys that designed Kotlin.

Prompted by that old post of mine I've been thinking about deleting while iterating in Python. I'm talking about deleting from the original object, not about creating a new collection with the not removed ones (that is as simple as just filtering the collection) Of course you can always use the "universal" solution of using a normal while loop and an index, and either traversing backwards or moving forward and being careful with how you update your position. But what about a for in and a iterator? If we try this:


l1 = ["Paris", "Toulouse", "Torino", "Xixon"]

for i, v in enumerate(l1):
    if v.startswith("T"):
        del l1[i]

print(l1)
['Paris', 'Torino', 'Xixon']


We don't get an exception as we get in .Net (for that .version thing that I explain in the old post), but we skip from the iteration the next item, so in this case Torino does not go through the validation and remains in the list.

Googling around I came across an amazing solution, that uses slice assignment to do an in place replacement of the whole original list with the filtered one.


l1 = ["Paris", "Toulouse", "Torino", "Xixon"]

l1[:] = [v for i, v in enumerate(l1) if not v.startswith("T")]
print(l1)
['Paris', 'Xixon']


I have to admit that I had never heard about slice assignment. The idea is that when we do:



l1 = ["a", "b", "c", "d"]
l2 = ["X", "Y"]

l1[1:3] = l2
print(l1)
# ['a', 'X', 'Y', 'd'] 

We are are replacing that slice of the list on the left with the list on the right.

No comments:

Post a Comment