Why doesn't iterating with `for` work while removing items from a list?

As with most problems, there are many ways to solve this one. It can be done with for or while.

Here’s your code with a few tweaks:

#sample lists
a = [2, 4, 5, 8, 9]
b = [2, 4, 6, 8, 10]
c = [1, 2, 3, 4, 5]

def delete_starting_evens(lst):
    for num in range(len(lst)):
        if lst[num] % 2 == 1: #looking for the first odd number
            return lst[num:] #return list from current index to end when odd number is found
    return [] #if we get to here, no odd number was found, so the whole list is even, and we return an empty list

#test the function with 3 sample lists    
for x in [a, b, c]:
    print(delete_starting_evens(x))

Output:

[5, 8, 9]
[]
[1, 2, 3, 4, 5]

This seems to provide the pass. But I don’t really feel comfortable about it.

def delete_starting_evens(lst):
for z in range(len(lst)):
if lst[0] % 2 != 0:
break
else:
lst.remove(lst[0])
return lst

It may pass the SCT (submission correctness test) but how does it do against other tests, such as,

  • emty list [],
  • all even numbers.

Thank you @mtf for the checking my code and providing feedback.
I agree, the empty list will be a problem. I added the empty list as additional condition, and now it works for both all even and empty list

def delete_starting_evens(lst):
for z in range(len(lst)):
if lst[0] % 2 != 0 or lst == :
break
else:
lst.remove(lst[0])
return lst

One question I have about the ‘while’ solution is, how is it iterating when there is no explicit formulation for it?

while len(lst) > 0:
if lst[0] % 2 == 0:
lst.pop(0)
else:
break
return lst

Did you test this against an empty list? I believe it will still raise an error.

Is the list always going to be reaching zero length? If not, then that will result in an infinite loop.

A while loop iterates through its code block repeatedly until the break condition is met. What change in state is taking place inside the loop (besides change in length)?

while lst[0] % 2 == 0:

That will keep the loop going until lst[0] is an odd number. You won’t need an if statement, and neither will you need a break. Just a pop statement in the loop, and a return statement after the loop.

I agree the while formulation is short and makes more sense.
Just for information, the ‘for’ loop also works as expected in all cases

Actually, I hadn’t considered that the loop doesn’t run if the list is empty. D’oh!

I’m still a little miffed as to how it doesn’t raise an error on all even numbers. At some point the list is empty and that should raise an IndexError when we try to poll lst[0]. I’ve confirmed that this does not happen, so cannot eplain why. It would sit better if we reversed the order of testing to,

if lst == [] or lst[0] % 2 != 0:

but it would also be nice to know why and error is not raised in your code. I suspect it is because of the loop never seeing an empty list. If that is the case, then we don’t need to check for an empty list.

>>> def delete_starting_evens(lst):
    for z in range(len(lst)):
        print (z)
        if lst[0] % 2 != 0:
            break
        else:
            lst.remove(lst[0])
    return lst

>>> delete_starting_evens([2,4,6,8,10])
0
1
2
3
4
[]
>>> delete_starting_evens([])
[]
>>> 

The index position 0 will never be empty, the most you items you can remove are the same as in the length of the list, after which the list will be empty. Give the code doesn’t try to access index position 0 after removing the last item, there is no reason it should throw an error.

>>> def delete_starting_evens(lst):
    for z in range(len(lst)):
        print("count: {0}".format(z))
        if lst[0] % 2 != 0:
            break
        else:
            print("List item at 0: {0}".format(lst[0]))
            lst.remove(lst[0])
    return lst

>>> delete_starting_evens([2,4,6,8,10])
count: 0
List item at 0: 2
count: 1
List item at 0: 4
count: 2
List item at 0: 6
count: 3
List item at 0: 8
count: 4
List item at 0: 10
[]
1 Like

My Code Doesn’t Work?
So I made a code that I thought would work for this challenge. It’s quite different from the solution but …

def delete_starting_evens(lst):
  for i in lst:
    if i %2 == 0:
      lst.pop(0)
  return lst

print(delete_starting_evens([4, 8, 10, 11, 12, 15]))
print(delete_starting_evens([4, 8, 10]))

The resulting output was [11,12,15] and [10]

I have no idea why the [10] was left over. Should the [10] have been popped out too?

When mutating the list we are iterating, all manner of things can go wrong, in particular, things are not where they are expected to be. Consider using an approach that does not alter the list being iterated.

What happens if we iterate a copy, not the actual list.

>>> def delete_starting_evens(lst):
  for i in lst[:]:
    if i % 2 == 0:
      lst.pop(0)
  return lst

>>> delete_starting_evens([4, 8, 10])
[]
>>> 

can please help me with the answers below, why below doesnt work ?

thanks!!11111

why would it work? You never modify the list, continue means literally that: do nothing and continue with the next iteration of the loop

Hi mtf,

lst = [4, 8, 10, 11, 12, 15]
#lst = [4, 8, 10, 12, 16]
#lst=
for i in lst:
if i % 2 == 0:
lst = lst[1:]
else:
break
print(lst)

The above code gives the following results
[11,12,15]

Doesn’t this assignment lst = lst[1:] mutate the lst list in the for loop and messes up the iterator index as in the following example?

#lst = [4, 8, 10, 11, 12, 15]
lst = [4, 8, 10, 12, 16]
for i in lst:
del lst[0]
print(lst)

[ 11, 12, 15]
[12, 16]

Thanks.

What does that do, and, does that involve changing the original list in a way that would disturb iteration?
You’re not going to want to do this, but for another reason, again apparent when considering what it does. If you carried this out manually, you’d do it differently.

So you need to know what things do before you use them, and then a whole lot of questions like that are already answered.

Any removal of elements in a list will mess with the iteration, especially if the removed element is at the front. It will cause the wrong element to be next. A while loop is better suited to this sort of operation.

From the earlier messages I understand it is better to use ‘while’ loop or use of a copy of the ‘list’ in ‘for’ loop for iteration to avoid issues. But I am quite intrigued why lst = lst[1:] worked in the following code? Is it because it is a copy assignment which do not disturb the original lst in the ‘for’ loop? But both have the same list name ‘lst’

lst = [4, 8, 10, 11, 12, 15]
#lst = [4, 8, 10, 12, 16]
for i, e in enumerate(lst):
  print(i, e)
  if lst[0] % 2 == 0:
    lst = lst[1:]
  else:
    break
  print(lst) 
print(lst)

As you can see the results have correct index and values. None are skipped.

0 4
[8, 10, 11, 12, 15]
1 8
[10, 11, 12, 15]
2 10
[11, 12, 15]
3 11
[11, 12, 15]

Compare the above with the following which gives wrong results.

lst = [4, 8, 10, 11, 12, 15]
#lst = [4, 8, 10, 12, 16]
for i, e in enumerate(lst):
  print(i, e)
  del lst[0]
  print(lst) 

0 4
[8, 10, 11, 12, 15]
1 10
[10, 11, 12, 15]
2 12
[11, 12, 15]

Thanks for your help.

Changing variables will have no effect on iteration when none of your variables are used for iteration

Changing values will have effect when those values are used for iteration

But how many copies do you make?

This is wrong (in the sense that you would not do this manually so neither should your program) in almost any context. Lists do not support deletion at the front

I guess you could say this for the whole statement, but that’s two separate actions, copying and assigning

The iterator is composed from the list and returns index-value pair tuples. i and e are the unpacked tuple values.

Eg.

(0, 4), (1, 8), (2, 10), (3 11), (4, 12), (5, 15)

That’s why you can do anything you want to the actual list.

That’s exactly what the whole thread is about. Can’t.