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?
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. Nowlst
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 over8
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 oflst.pop[0]
). Nowlst
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. Nowlst
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 over8
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 oflst.pop[0]
). Nowlst
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 oflst.pop[0]
). Nowlst
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.