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

Thank you @mtf - visual now helped to clear the tangled-up logic in my mind, thank you for your time and patience!

2 Likes

I have thought about this and now think I understand it.
Writing the following helped me to understand it.
Is the following a correct illustration of what happens?

lst = [4, 5, 6, 7, 8, 9]
for number in lst:
INDENTlst.pop(0)
print(lst)
#the order of loops for this is:
#loop 1. -> 4 is popped
#loop 2. -> 5 moves to the slot vacated by the 4 (as lst[0] is currently empty)
#loop 3. -> 5 is popped
#loop 4. -> 6 moves to the slot vacated by the 5
#loop 5. -> 6 is popped
#loop 6. -> 7 moves to the slot vacated by the 6
#you’re out of loops
#lst is now [7, 8, 9]

the loop doesn’t make 6 iterations:

lst = [4, 5, 6, 7, 8, 9]
for number in lst:
    print "in loop:", lst
    lst.pop(0)
print(lst)

it only make threes, the occupation of an empty slot happens before going to next iteration of the loop

But there are 6 items in the list. Does that not mean 6 loops in a for loop for that 6-itemed list?

no, there are 6 items in your list before you start the loop

you mean 6 iterations of the loop? The answer: no.

because you remove items from the list, the loop won’t have to make 6 iterations to reach the end of the list

I’ve did it another way without using pop(), here is my workaround:

#Write your function here

def delete_starting_evens(lst):
  newlst = []
  for i in lst:
    if i%2==0:
      continue
    elif i%2!=0:
      newlst = lst[lst.index(i):]
      break
  return newlst

#Uncomment the lines below when your function is done
print(delete_starting_evens([4, 8, 10, 11, 12, 15]))
print(delete_starting_evens([4, 8, 10]))
1 Like

Hi, I was just wondering why my code doesn’t work:

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

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

My code seems to be printing [12,15], slicing out 11 as well.

1 Like

When you do this, you shift the list to the left. However, i is moving to the right, steadfastly.

1 Like

How about my solution?

def delete_starting_evens(lst):
  empty = []
  for element in lst:
    if element % 2 != 0:
      index = lst.index(element)
      return lst[index:]
  return empty
1 Like

Putting your trust in object.index() is a mighty big play. Mind, we are only looking for the first occurrence, so in this one instance you could pull it off.

1 Like

so is this wrong solution for this problem?
but i check every possible test cases, my code works fine!

1 Like

“Wrong” did not come into question. It is a viable solution, not to be discounted. In fact it is literal in every sense. We do want the index of the first odd number. Perhaps I was a bit quick to jump on the don’t use .index train.


As that goes, I’m more trusting of the enumerate function to give me the correct index of the value it currently sees. It never messes up on its position in the list.

1 Like

When I used my for loop i tried to make a range list for list length. I was curious to know if this was the most efficient way to use the for loop in this case.

#Write your function here
def delete_starting_evens(lst):
  new = []
  for i in range(len(lst))):
    if lst[i] % 2 == 1:
      new = lst[i:]
      break
  return new

#Uncomment the lines below when your function is done
print(delete_starting_evens([4, 8, 10, 11, 12, 15]))
print(delete_starting_evens([4, 8, 10]))
1 Like

So I have this code:

def delete_starting_evens(lst):
for num in lst:
if num % 2 > 0:
continue
if len(lst) == 0:
break
lst.pop(0)
if len(lst) == 1 and num % 2 == 0:
lst.pop()
return lst

It works for this:

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

But it’s not working for these:

print(delete_starting_evens([4, 8, 10, 6, 15, 24, 20, 1]))

print(delete_starting_evens([6, 8, 10, 23]))

On the fist of the two above the print out starts at 6, and the second at 10… I just can’t get my head to understand why the first print command actually prints the list starting at 11?

for is an iterator, and like all iterators has a next() method behind the scenes. That method always points to the item or index following the one currently being visited. When we remove an element at the current position, the list shifts left, but not the next pointer. It stays where it is.

If we think about it, once this happens, we are never popping the item that we are actually looking at, but one to the left of that. I know, this is a poor explanation, but the bottom line is, don’t remove items from a list that being iterated with for unless you have the mechanics down pat.

Suggest using while since it does not have a next() method. It just stays on the loop as long as the condition is met.

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

Play around with that and see what other forms you come up with.

2 Likes

Hey thanks for the explanation, it makes sense now, I did use the while loop and see how shorter it can be!

My solution was accepted by the console but I like to play more with my code to see if there’s a better way of doing things, only because of this I was able to see my solution didn’t really work for all the cases and so I started thinking what was going on.

Thanks again!

1 Like
>>> lst = [4, 5, 6, 7, 8, 9]
>>> for index, number in enumerate(lst):
    print ("in loop:", lst)
    lst.pop(0)

    
in loop: [4, 5, 6, 7, 8, 9]
4
in loop: [5, 6, 7, 8, 9]
5
in loop: [6, 7, 8, 9]
6
>>> lst
[7, 8, 9]
>>> index
2
>>> 

There is no index[3] so the loop terminates.

Rather than trying to make for do something it can’t, just use while.

I appreciate that using .pop() in a for loop is not the way to go , I just wanted to understand why it creates the output it does.
I’ve understood everything in the Python3 course so far, but this one has been a bit difficult to get my head around to be honest!
I think I get it though.
The for loop goes through the cycle of iterating through indices [0], [1], [2] and [3] then it is outside the list and can’t continue.

When the .pop() action takes place, the list becomes something new, but the for loop continues to cycle through [0], [1], [2] and [3] as the list keeps changing.

list = [4, 5, 6, 7, 8, 9]
#ins= 0, 1, 2, 3, 4, 5
for loop cycle is for index 0 (4)
list[0] is popped (4)

list = [5, 6, 7, 8, 9]
#ins= 0, 1, 2, 3, 4
for loop cycle is for index 1 (6)
list[0] is popped (5)

list = [6, 7, 8, 9]
#ins= 0, 1, 2, 3
for loop cycle is for index 2 (8)
list[0] is popped (6)

list = [7, 8, 9]
#ins= 0, 1, 2
for loop cycle can’t continue because there is no index 3

Thanks for helping me to understand this mtf and stetim94!

1 Like

def delete_starting_evens(lst):
for x in lst:
if x%2==0:
lst=lst[1:]
return lst

i’m getting a return of [12,15]? I’m not sure why this isn’t returning the 11 as well.

Mutating a list in a for loop can be tricky and often messes things up.

Try iterating over a copy of the list so that it does not change when you alter the list itself.

for x in lst[:]

or

for x in lst.copy()

Now that list stays unchanged when you mutate lst.