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

Hello rrtocode,

I have also used “for” and slicing and in this case the code works. Didn’t really understand from the replies why in this case it does and in the others it doesn’t.

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

wrt the title, you’re not removing values, you’re making copies
(also I suggest making a single copy once you’ve found the location of the first odd value (or end), instead of making a copy during each iteration (you’re not using it anyway, you’re only using the last one)

1 Like


Can someone explain this?

Picture one is my trial. Picture two is the standard answer. I try to get an empty list by using my for loop. However, it seems that the still satisfy the condition of my for loop and somehow make the become a None object. Can someone please explain this?

Be sure that your return statement is not inside the loop.

Thanks. I just noticed that.

1 Like

I don’t really understand the output for this code

lst = [4, 8, 10, 11, 12, 15]
for i in lst:
  del lst[0]
  print(lst)
[8,10,11,12,15]  #i = 4 (index 0)
[10,11,12,15] #i = 10 (index 1 above)
[11,12,15] #i = 12(index 2 above)
#the loop will stop at this point, as there is no index 3

How come it stops when there is no index 3?

for i in lst:
  del lst[0]

Isn’t it deletes the index 0 in the list everytime, like:

[8,10,11,12,15]  
[10,11,12,15] 
[11,12,15]
[12,15]
[15]
[]

??
Thanks

here:

[8,10,11,12,15]  #i = 4 (index 0)

as you can see, 8 is now index 0 (given we can’t have empty slots in our list) while the loop moves on to the next index (1).

Removing from the same loop as your looping/iterating over is tricky business.

Sorry, I still not really understand.
Thanks

under the hood, python keeps track where the loop is using indexes

so when you remove the value 4 from index 0, we create an empty slot. Which can’t be, so everything shifts one slot to the left (the value 8 from index 1 moves to index 0, the 10 from index 2 moves to index 1 and so forth)

but the loop goes to the next index (1), essentially skipping the 8 which is now at index 0)

due to this shifts happening when you remove items, your loop will run out without having removed everything

like i said, removing from the list you are looping over is a terrible idea

next time, please explain what you don’t understand.

1 Like

very good implementation.

Really? It’s got the index method in it. Is it even credible?

A lot of smoke.

Say we just find the index of the first odd? How hard is that?

This one wrong…why you are checking the last element and removing all if it’s found to be even. Your written code will remove all elements no matter what just because one condition is satisfying that’s last element is even…do you think this is correct logic to to remove elements from beginning until odd is reached.?
try your written code for this print(delete_starting_evens([4,8,12,15,14]))
you code will print which is empty list but it should give [15,14]

can you please tell me the difference between these two…for x in lst: and for x in lst[ : ]… why one is working correctly while other giving incorrect result.

def delete_starting_evens(lst):
  for x in lst:
    if x % 2 == 0:
      lst.remove(x)
    else: break
  return lst
  
print(delete_starting_evens([2,4,18]))
def delete_starting_evens(lst):
  for x in lst[:]:
    if x % 2 == 0:
      lst.remove(x)
    else: break
  return lst
  
print(delete_starting_evens([2,4,18]))```

List slicing should come up soon if it hasn’t already. A slice is a virtual copy of a segment. It relies upon the colon syntax to identify the start and end. [:] is a complete shallow copy of the list.

When removing elements in a for loop we end up skipping some. By iterating over a copy of the list, it doesn’t change, only the list we are removing from changes. That prevents skipping elements.

Since x us a value, not an index, once we identify it in the copy, we can remove it from the original.

you means [ : ] make similar list as .copy() and both don’t affected if the original list (from which they are generated) changed.?

Correct, there is no change to the original list. [:] is the same as .copy(), though we need to check if the latter is also only a shallow copy. If not, then that will be their difference. A slice is a shallow copy.

>>> a = [1,2,3,4,5,6,7,8,9]
>>> b = a
>>> c = a[:]
>>> a.remove(5)
>>> b
[1, 2, 3, 4, 6, 7, 8, 9]
>>> c
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> 

Notice how c is unchanged but b is not? That’s because it is a shared reference with a. c is a virtual copy.

>>> a = [1,2,3,4,5,6,7,8,9]
>>> c = a[:]
>>> for x in c:
	if x % 2:
		a.remove(x)

		
>>> a
[2, 4, 6, 8]
>>> c
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> 
1 Like

yeah…exactly now I understand with your great illustration. Thank you. @mtf

1 Like

This is amazing! Thank you so much. It looks so simple afterwards, but it’s genius)

Although the “while” loop is a much better suited loop for this exercise, the “for” loop might work as well (see below):

def delete_starting_evens(lst):
	for x in lst:
		if x % 2 == 0 and len(lst) > 0:
			lst = lst[1:]
		elif x % 2 != 0 and len(lst) > 0:
			return lst
		else:
			lst.pop(0)
	return lst

We know that else clause isn’t running or it would raise an exception…

>>> a = []
>>> a.pop(0)
Traceback (most recent call last):
  File "<pyshell#208>", line 1, in <module>
    a.pop(0)
IndexError: pop from empty list
>>> 

So why have it?