Iterating over lists while modifying them

We can remove an element of a list using the del operator:

In [1]:
L = [3, 4, 2, 1, 2]
del L[1]
In [2]:
L
Out[2]:
[3, 2, 1, 2]

del L[1] removes the element of list L at index 1.

Suppose that we want to remove all elements equal to 4.0 from the list L. Here is something we can try:

In [3]:
L = [2.3, 3.9, 4.0, 1.3, 2.3]
for i in range(len(L)):
    if L[i] == 4.0:
        del L[i]        
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-3-4ff422aefadd> in <module>()
      1 L = [2.3, 3.9, 4.0, 1.3, 2.3]
      2 for i in range(len(L)):
----> 3     if L[i] == 4.0:
      4         del L[i]

IndexError: list index out of range

Why did we get an error? Because when going for i in range(len(L)), we said we want i to be 0, 1, 2, 3, 4. But once the list has a 4.0 removed, it's no longer of length 5. So trying to compare L[4] to 4.0 would create an error.

That's a problem specific to trying to use for-loops. Cna we use a while-loop instead? Here is a first try:

In [4]:
L = [2.3, 3.9, 4.0, 4.0, 1.3, 2.3]
i = 0
while i < len(L):
    if L[i] == 4.0:
        del L[i]
    i += 1
In [5]:
L
Out[5]:
[2.3, 3.9, 4.0, 1.3, 2.3]

One of the 4.0's didn't get removed! Why? because when we removed L[2], i became 3, but at that point L[3] became 1.3, because the 4.0 was removed (the list was L = [2.3, 3.9, 4.0, 4.0, 1.3, 2.3]). So we skipped over the second 4.0. There is a solution in this case: only increment i we haven't just removed an element:

In [6]:
L = [2.3, 3.9, 4.0, 4.0, 1.3, 2.3]
i = 0
while i < len(L):
    if L[i] == 4.0:
        del L[i]
    else:
        i += 1
In [7]:
L
Out[7]:
[2.3, 3.9, 1.3, 2.3]

This worked, but in general you should avoid trying to modify mutable objects such as lists while iterating over them: it's a recipe for trouble.