Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Removing from a list while iterating over it

The following code:

a = list(range(10))
remove = False
for b in a:
    if remove:
        a.remove(b)
    remove = not remove
print(a)

Outputs [0, 2, 3, 5, 6, 8, 9], instead of [0, 2, 4, 6, 8] when using Python 3.2.

  1. Why does it output these particular values?
  2. Why is no error given to indicate that underlying iterator is being modified?
  3. Have the mechanics changed from earlier versions of Python with respect to this behaviour?

Note that I am not looking to work around the behaviour, but to understand it.

like image 897
Matt Joiner Avatar asked Jun 28 '11 02:06

Matt Joiner


People also ask

Can we remove from list while iterating?

An element can be removed from a Collection using the Iterator method remove(). This method removes the current element in the Collection. If the remove() method is not preceded by the next() method, then the exception IllegalStateException is thrown.

Can we remove element from list while iterating Java?

In Java 8, we can use the Collection#removeIf API to remove items from a List while iterating it.

How do you remove from set while iterating?

Using an iterator We can use the remove() method provided by the Iterator interface that removes the latest element returned by the iterator. Please note we should not modify the set after the iterator is created (except through the iterator's own remove method); otherwise, a ConcurrentModificationException is thrown.


1 Answers

I debated answering this for a while, because similar questions have been asked many times here. But it's just unique enough to be given the benefit of the doubt. (Still, I won't object if others vote to close.) Here's a visual explanation of what is happening.

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]       <-  b = 0; remove? no
 ^
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]       <-  b = 1; remove? yes
    ^
[0, 2, 3, 4, 5, 6, 7, 8, 9]          <-  b = 3; remove? no
       ^
[0, 2, 3, 4, 5, 6, 7, 8, 9]          <-  b = 4; remove? yes
          ^
[0, 2, 3, 5, 6, 7, 8, 9]             <-  b = 6; remove? no
             ^
[0, 2, 3, 5, 6, 7, 8, 9]             <-  b = 7; remove? yes
                ^
[0, 2, 3, 5, 6, 8, 9]                <-  b = 9; remove? no
                   ^

Since no one else has, I'll attempt to answer your other questions:

Why is no error given to indicate that underlying iterator is being modified?

To throw an error without prohibiting many perfectly valid loop constructions, Python would have to know a lot about what's going on, and it would probably have to get that information at runtime. All that information would take time to process. It would make Python a lot slower, in just the place where speed really counts -- a loop.

Have the mechanics changed from earlier versions of Python with respect to this behaviour?

In short, no. Or at least I highly doubt it, and certainly it has behaved this way since I learned Python (2.4). Frankly I would expect any straightforward implementation of a mutable sequence to behave in just this way. Anyone who knows better, please correct me. (Actually, a quick doc lookup confirms that the text that Mikola cited has been in the tutorial since version 1.4!)

like image 194
senderle Avatar answered Sep 22 '22 22:09

senderle