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

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.

Oh, I didn’t realize that was what I was doing. Is there a reason why defining the parameters of the list with a colon and brackets changes how the list is manipulated in the function?

1 Like

The expression lst[:] is known as a full slice, as in the entire list. We will not be mutating that copy, only the original list. We just use it for iterating so we get the complete list with no missing members.

The copy is only temporary and does not take up any memory once the loop is complete.

So I realized that my code was iterating correctly, but my code was reading the ‘11’ and iterating through and saw the 12 then removed the first index, which would be ‘11’ at that point, then removed it and stopped at 15 because it was the end of the list. I’m just not clear on how to keep it from iterating past the last even integer

>>> a = [4, 8, 10, 11, 12, 15]
>>> for x in a[:]:
	if x % 2 == 0:
		a = a[1:]
	else:
		break

	
>>> print (a)
[11, 12, 15]
>>> 

Looks like we need to break out of the loop when we find an odd number.

Interesting that above we keep the loop going while x is divisible by two. Can we write logic that is explicit about this, rather than using for?

Yes.

>>> a = [4, 8, 10, 11, 12, 15]
>>> while a[0] % 2 == 0:
	a = a[1:]

	
>>> a
[11, 12, 15]
>>> 

Now we are not iterating a list, as such, just focusing on the first element of the list. Mutation is part and parcel of iteration.

Warning: This one will throw an error on an empty list.

>>> a = [4, 8, 10, 14, 12, 16]
>>> while a[0] % 2 == 0:
	a = a[1:]

	
Traceback (most recent call last):
  File "<pyshell#28>", line 1, in <module>
    while a[0] % 2 == 0:
IndexError: list index out of range
>>> a = []
>>> while a[0] % 2 == 0:
	a = a[1:]

	
Traceback (most recent call last):
  File "<pyshell#31>", line 1, in <module>
    while a[0] % 2 == 0:
IndexError: list index out of range
>>> 

The for loop protects us from that exception.

>>> a = [4, 8, 10, 14, 12, 16]
>>> for x in a[:]:
	if x % 2 == 0:
		a = a[1:]
	else:
		break

	
>>> a
[]
>>> for x in a[:]:
	if x % 2 == 0:
		a = a[1:]
	else:
		break

	
>>> a
[]
>>> 

What if…

We just return the slice that starts at the first odd number?

>>> a = [4, 8, 10, 11, 12, 15]
>>> for x in a:
	if x % 2 != 0:
		a = a[a.index(x):]
		break
else:
	a = []

	
>>> a
[11, 12, 15]
>>> a = [4, 8, 10, 14, 12, 16]
>>> for x in a:
	if x % 2 != 0:
		a = a[a.index(x):]
		break
else:
	a = []

	
>>> a
[]
>>> 
1 Like

I woke up this morning with the immediate thought to use a break once I found the odd. I’m glad that your response was also to do that! Thank you so much for your help!

1 Like

def delete_starting_evens(lst):
while lst[0] % 2 == 0
lst.remove(lst[0])
return lst

def delete_starting_evens2(lst):
for i in lst:
print(i)
if i % 2 == 0:
lst.remove(i)
return lst

why these wouldn’t work
and how to make it work
as the second function, it will also returns 8, but 8 is a even number, so confuse

and when i run this code,
the first call was successful, but the second call returns [10]

def delete_starting_evens2(lst):
for lst[0] in lst:
if lst[0] % 2 == 0:
lst.remove(lst[0])
return lst

One should expect a little weirdness when mutating the list we are iterating over. It upsets the internal indexing of the iterator.

If you wish to remove items from a list while iterating, then set up the loop on a copy of the list, and then change the original. This will give the iterator a fixed sequence that does not change during the loop.

I don’t believe that is a valid signature line for a loop. We cannot use a subscripted variable.

for x in lst.copy():
    # ...

Why does the following code only return 11?

def delete_starting_evens(lst):
	for i in range(len(lst)):
		if lst[i] % 2 == 0:
			continue
		else:
			return lst[i]

That doesn’t look quite right. Should there even be a return in the loop?

I don’t know, but I think I solved it with a return in the loop.

def delete_starting_evens(lst):
	for i in range(len(lst)):
		if lst[i] % 2 == 0:
			continue
		else:
			return lst[i:]
1 Like

Below is my solution. Can anyone help me understand why this doesn’t work if “return lst” is part of the “while” loop as opposed to flush with the “while” loop?

[spoiler]def delete_starting_evens(lst):
  i = len(lst)
  while i > 0 and lst[0] % 2 == 0:
    for num in lst:
      print(lst)
      lst.pop(0)
      i = i - 1
  return lst[/spoiler]

Because once the while loop is entered, the for loop takes over and pops every zeroeth element with no reference at all to the while conditions.

As has been pointed out in this discussion, it generally isn’t a good idea to modify a list that you are iterating over, especially at the beginning of the list. There are no doubt exceptions, but doing so will often yield unexpected results.

1 Like

Here’s my slicing solution. Seems to get the job done! :slight_smile:

#Write your function here
def delete_starting_evens(lst):
  new_start_index = 0
  for num in lst:
    if num % 2 == 0:
      new_start_index += 1
    else:
    	break
  lst = lst[new_start_index:]
  return lst

#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]))
print(delete_starting_evens([]))
print(delete_starting_evens([5, 3, 1]))

sorry again but i dont seem to understand. why doesnt this code empty the whole list deleting the first index each time? could you explain it again? maybe my english arent good enough. im really sorry

Yeah, so with each pass a for loop moves to the next index. So it will check index 0, 1, 2, etc. The problem is that when we delete index 0, this adjusts the indexes of the rest of the list, so index 1 is the new index 0. The for loop doesn’t know this however and moves on to check index 1 which is now the value originally index 2 since they slid down. This means it will hit the end of the list without having gone through each item.
Try running some examples and having the code print at each step so you can see how it changes.

1 Like

thank you very much catower. you really cleared this up for me!!! thanks again!!!

1 Like

I struggled a lot with this one and ended up into something that I would appreciate you giving me your opinion about its efficiency and if it can be called an accepted solution to this exercise.

def delete_starting_evens(lst):
  try:
    while lst[0] % 2 == 0:
      lst.pop(0)
    return lst
  except:
    a = []
    return a

I used try: and except: creating a new empty list addressing the pop() error where it can’t work at a single element list due to not having anything to replace it with.

edit:

sorry, the calling lists are:

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

Just make sure it doesn’t try to pop anything when the list is empty. How about:

def delete_starting_evens(lst):
    while <condition_1> and <condition_2>:
        lst.pop(0)
    return lst

Well, this is not exactly what I asked for but thank you anyway @patrickd314!

2 Likes