Python Code Challenges: Loops - Exercise 3. Delete Starting Even Numbers

Hi guys,

I’m just trying to understand why .pop() is unable to remove the remaining element from a list to make it empty.

def delete_starting_evens(lst):
  for num in lst:
    if num % 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 result I get is:

[11, 12, 15]
[10] #was expecting this to be empty

  1. The first result was expected, however, I was expecting the second result to be an empty list. What am I missing here?
  2. For the solution code noted below, wouldn’t the last iteration return an out of range index error? During the last iteration on the second list of all even numbers, the while loop conditions would be met, but wouldn’t the lst[1:] be out of range since the only element left is at 0?
def delete_starting_evens(lst):
  while (len(lst) > 0 and lst[0] % 2 == 0):
    lst = lst[1:]
  return lst

Thanks in advance for your help!

There’s a couple of potential issues here; one thing to avoid is altering the object you are iterating through (this is almost always unintended behaviour); if you’re unsure how this works try printing num on each iteration and focus on the expected and received order.

Secondly try the following list- [1, 2, 4, 6]. Double check the requirements about starting evens.

Regarding q2 slices that are outside the actual indices of the list just return empty lists instead of throwing errors. Try a little debugging with print if you want to see that in action.

3 Likes

The for loop iterates based on the index of the initial list, when you modify that list inside of the loop it doesn’t update. As a result it runs out of valid indices to iterate on before your list is empty.
Try this code out:

def delete_starting_evens(lst):
  for num in lst:
    if num % 2 == 0:
      print(lst) 
      print(lst[0], num)
      lst.pop(0)
  return lst


print(delete_starting_evens([4, 8, 10, 12, 14, 16, 18, 20, 22]))
1 Like

This blew my mind but finally understood the issue. It is very scary to come across an issue like this, but knowing that there’s a community to ask is very reassuring!

I ran the code with a few extra prints and found that the loop does indeed run out of indices.

def delete_starting_evens(lst):
  for num in lst:
    print(num, "ifprior")
    print(lst, "ifprior")
    if num % 2 == 0:
      print(lst, "ifpost") 
      print(lst[0], num, "ifpost")
      lst.pop(0)
      print(lst, "end") 
  return lst

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

In the for loop, it starts iterating at index 0 with the original list [4, 8, 10, 12], during the “if” conditional statement, the .pop(0) method removes the element at index 0 leaving us with list [8, 10, 12].

This is where it gets interesting, when we go through the “for” loop again, it continues at index 1, but with the updated list, num is 10 and would be the same as lst[1].

During the last iteration, the list has a length of 2 (elements at index 0 and 1), while the “for” loop is trying to iterate over element at index 2, which would no longer exist, which then causes unexpected behaviour.

4 ifprior
[4, 8, 10, 12] ifprior
[4, 8, 10, 12] ifpost
4 4 ifpost
[8, 10, 12] end
10 ifprior
[8, 10, 12] ifprior
[8, 10, 12] ifpost
8 10 ifpost
[10, 12] end
[10, 12]

Thank you so much for your input guys! :grinning:

Glad to hear it’s marking more sense, perhaps you’ve already solved this but if not please do test- [1, 2, 4, 6] as no example to date would correctly deal with it.

If you wanted a bit more detail about altering the object you’re iterating through then the tutorials on the docs go into a little more detail on what the for loop actually iterates through-
https://docs.python.org/3/tutorial/classes.html#iterators
The example relies on classes which won’t have been introduced yet but it may be worth looking back on at some point.

An additional useful quote from- 8. Compound statements — Python 3.9.4 documentation
“There is a subtlety when the sequence is being modified by the loop (this can only occur for mutable sequences, e.g. lists). An internal counter is used to keep track of which item is used next, and this is incremented on each iteration. When this counter has reached the length of the sequence the loop terminates. This means that if the suite deletes the current (or a previous) item from the sequence, the next item will be skipped (since it gets the index of the current item which has already been treated). Likewise, if the suite inserts an item in the sequence before the current item, the current item will be treated again the next time through the loop.”

1 Like

Yup, the documented quote you provided is exactly what the behaviour was. Thanks for your time :+1:t2: