Can't clear list why?

Hello, this is my first post. The challenge is to clear the list, but somehow it does not wnat me to remove the last element, although 10 is devisible by 2. I also tried to use .clear, but also that doesn’t seem to remove it. Why?

#Write your function here def delete_starting_evens(lst): for i in lst: if i % 2 == 0 : lst.pop(0) elif len(lst) == 1 and i % 2 == 0: lst.clear() else: break 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]))

for i in lst: basically says that the for loop is going to iterate over lst. It will start from the first element of lst and keep iterating until there are no more elements left i.e. until it reaches the end of the list/iterable.

The problem is that by removing/inserting elements to the list you are iterating over, you are changing the length of the list while you are iterating over it. This can cause unintended consequences such as elements being skipped or being iterated over more than once.

Your code is:

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

Consider the function call where the argument is [4, 8, 10]
Taking liberties, a rough sketch of the loop is:

  • Loop starts and asks the question: “Is there a next element to be iterated over OR has the end of list been reached?”. It looks at index 0 to decide the answer. At this index, there is an integer element 4. The iteration begins and per your code, 4 is removed from the list. Now lst is [8, 10]
  • Next iteration. Loop again asks question: “Next Element? OR End of List?”. But it has already looked at index 0, so it looks at index 1 to decide the answer. At this index, there is the integer element 10. It skipped over 8 because as far as the loop is concerned, it has already taken care of the element at index 0. Iteration begins and per your code, 8 is removed from the list (because of lst.pop[0]). Now lst is [10]
  • Next iteration. Loop again asks question: “Next Element? OR End of List?”. But it has already looked at index 1, so it looks at index 2 to decide the answer. This index is beyond the bounds of the list, so for decides it has reached end of list and finishes.

Consider the function call where the argument is [4, 8, 10, 11, 12, 15]

  • Loop starts and asks the question: “Is there a next element to be iterated over OR has the end of list been reached?”. It looks at index 0 to decide the answer. At this index, there is an integer element 4. The iteration begins and per your code, 4 is removed from the list. Now lst is [8, 10, 11, 12, 15]
  • Next iteration. Loop again asks question: “Next Element? OR End of List?”. But it has already looked at index 0, so it looks at index 1 to decide the answer. At this index, there is the integer element 10. It skipped over 8 because as far as the loop is concerned, it has already taken care of the element at index 0. Iteration begins and per your code, 8 is removed from the list (because of lst.pop[0]). Now lst is [10, 11, 12, 15]
  • Next iteration. Loop again asks question: “Next Element? OR End of List?”. But it has already looked at index 1, so it looks at index 2 to decide the answer. At this index, there is the integer element 12. Iteration begins and per your code, 10 is removed from the list (because of lst.pop[0]). Now lst is [11, 12, 15]
  • Next iteration. Loop again asks question: “Next Element? OR End of List?”. But it has already looked at index 2, so it looks at index 3 to decide the answer. This index is beyond the bounds of the list, so for decides it has reached end of list and finishes. It is just by good luck rather than sound logic that the output of [11, 12, 15] turns out to be correct.
    Suppose instead of [4, 8, 10, 11, 12, 15], the argument was [4, 7, 10, 11, 12, 15].
    Your function would return the output [11, 12, 15] even though the correct answer is [7, 10, 11, 12, 15]

Bottom Line: Don’t mutate a list while you are iterating over it unless you are absolutely certain that it is safe to do so.

Try adding a print statement at the top of the loop. That will allow you to see which element of the list is being iterated over.

def delete_starting_evens(lst):
  for i in lst:
    print(i)     # <-- For debugging and testing
    if i % 2 == 0 :
    ...

You could iterate over a copy of the original list. Suppose you tried something like,

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

This won’t work, because copied_lst = lst doesn’t actually create a copy of the list. Both variables lst and copied_lst hold the reference to the same list in memory. If you modify lst, you are actually modifying copied_lst and vice versa.

lst = [4, 9, 12]
copied_lst = lst
lst[2] = 100
print(lst)  
# Output: [4, 9, 100]
print(copied_lst) 
# Output: [4, 9, 100]

To create a copy of lst, you can use slicing or the list() type constructor (and a few other ways).

# Slicing
lst = [4, 9, 12]
copied_lst = lst[:]
lst[2] = 100
print(lst)  
# Output: [4, 9, 100]
print(copied_lst) 
# Output: [4, 9, 12]

# Type Constructor
lst = [4, 9, 12]
copied_lst = list(lst)
lst[2] = 100
print(lst)  
# Output: [4, 9, 100]
print(copied_lst) 
# Output: [4, 9, 12]

So, if you edit your code to the following, it will work:

def delete_starting_evens(lst):
  for i in lst[:]:    # <-- Iterating over copy of original list
    if i % 2 == 0 :
      lst.pop(0)   # <-- Changes to original list don't affect copy of list 
    else:
      break
  return lst

Slicing or the list type constructor actually create shallow copies. That is good enough for this challenge because the argument is expected to be a list of numbers. However, if there was a list nested within a list, then mutating the nested list in the original list would also mutate the copied list.

lst = [4, 9, [12, 32, 22]]
copied_lst = lst[:]
lst[2][1] = 100
print(lst)  
# Output: [4, 9, [12, 100, 22]]
print(copied_lst) 
# Output: [4, 9, [12, 100, 22]]

For such cases, we can use deepcopy from the copy module after importing (copy — Shallow and deep copy operations — Python 3.11.1 documentation).

However, for this challenge, shallow copying is adequate to accomplish the task.

1 Like