Codecademy Forums

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

Question

In the context of this exercise, using a for loop to iterate through the list seems like a good solution. However this ends up generating wrong solutions, why doesn’t this work?

Answer

To understand why for iteration breaks in this lesson we need to break down what happens as the list runs:

lst = [4, 8, 10, 11, 12, 15]
for i in lst:
  del lst[0]
  print(lst)

This code will let us take a look at how the code breaks down as it iterates through the list. So the results will be:

[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

So because the list is shifted each time it runs, an element will be skipped with each pass and cause it to stop. In our example we would have expected to empty the list by the end but that was not the case. To give a hint regarding the code challenge, since for doesn’t work if the list changes, what if you used while? What kind of conditions would you be looking to maintain while moving through the list?

Feel free to discuss below and help each other with understanding this concept!

1 Like

I used “lst.pop(0)” instead of “lst = lst[1:]”, then I got the wrong result. Does anyone can tell me why? Thank you!

#Write your function here
def delete_starting_evens(lst):
–for i in lst:
----if i % 2 == 0:
------lst.pop(0)
----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]))

output:
[11, 12, 15]
[10]

2 Likes

(very late reply)

Modifying the list will interfere with the loop. You move elements when you remove at the front, while at the same time the loop progresses forwards.

Also, all that moving makes it very inefficent. If you reversed it first, then you could pop until the last value is no longer even. Popping is fast and the loop wouldn’t be affected (however you should not use a for-loop because that doesn’t match what you’re doing, which is repeating something until a condition’s outcome changes)

1 Like

I used a similar coding scheme (see below) and mine didn’t iterate past the first variable - not sure why.

@ionatan Sorry but I don’t understand your answer. How is this code modifying the list in a way that different then the .pop() command? Also, what do you mean by reversed first?

Finally, could you write:

def delete_starting_evens(lst):
  while len(lst) and lst[0] % 2 == 0:
  lst.pop(0) 

return lst 

My code attempt:

#Write your function here
def delete_starting_evens(lst):
  for nums in lst:
    if len(lst) >= 0 and lst[0] % 2 == 0:
        lst.pop(0)
        while lst[0] % 2 == 0:
          return lst 
        else:
          return 'NO DICE'

#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([7, 8, 10]))

Peter, why did you reject the lovely first one (which needs only a bit of work on the indentation) for the well, shall we say, “complicated”, second??

The second, for one thing, uses the ‘while - else’ construction, something you will rarely see. Under what condition do you want to see “NO DICE”?

Follow the code with lst = [4, 8, 11]

Grab the first number from the list. if it is even, pop lst[0]. Now lst = [8, 11]
Now, lst[0] is 8, so the next block

while lst[0] % 2 == 0:  # This condition is true since lst[0] is 8
          return lst 

executes, returning lst, or [8, 11].

Remember that return returns the desired value and then halts the function.

… and what is the for loop supposed to accomplish?

Ok to clarify the second block of code was my first attempt at defining the function. The first block was my attempt to rewrite the solution from memory (which I also managed to screw up).

The FOR operation was just so that the function would run through each individual item in LST. Then in each element IF len(lst) is >0 and odd remove index 0 WHILE LST[0] is odd. I thought it made sense.

The ELSE item was just to give the function something to return that indicated the string did not begin with an even number.

The list.pop() method will raise an exception if list length is zero.

if len(lst) > 0

The use of a while loop inside a for loop can be a tricky thing to manage, but first we should determine if that much logic is even needed. What is the most we need to iterate the list one time?

Answer: A single loop.

As for the textual return, that would never be advisable if the return is expected to be a list. It would be feeding garbage back to the caller and raise an exception as soon as a list method was called on that object. It would certainly be messing up the data, nonetheless.

>>> a = "string"
>>> b = a.copy()

Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    b = a.copy()
AttributeError: 'str' object has no attribute 'copy'
>>> b = list(a).copy()
>>> b
['s', 't', 'r', 'i', 'n', 'g']
>>> 

Here is a list of list methods and built in functions that work on lists, and perhaps other object types, as well.

Method Description
Python List append() Add Single Element to The List
Python List extend() Add Elements of a List to Another List
Python List insert() Inserts Element to The List
Python List remove() Removes Element from the List
Python List index() returns smallest index of element in list
Python List count() returns occurrences of element in a list
Python List pop() Removes Element at Given Index
Python List reverse() Reverses a List
Python List sort() sorts elements of a list
Python List copy() Returns Shallow Copy of a List
Python List clear() Removes all Items from the List
Python any() Checks if any Element of an Iterable is True
Python all() returns true when all elements in iterable is true
Python ascii() Returns String Containing Printable Representation
Python bool() Converts a Value to Boolean
Python enumerate() Returns an Enumerate Object
Python filter() constructs iterator from elements which are true
Python iter() returns iterator for an object
Python list() Function creates list in Python
Python len() Returns Length of an Object
Python max() returns largest element
Python min() returns smallest element
Python map() Applies Function and Returns a List
Python reversed() returns reversed iterator of a sequence
Python slice() creates a slice object specified by range()
Python sorted() returns sorted list from a given iterable
Python sum() Add items of an Iterable
Python zip() Returns an Iterator of Tuples

https://www.programiz.com/python-programming/methods/list

Why this does not work??

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

Blockquote

1 Like

i think one of the function calls provided by the exercise can help:

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

8 isn’t remove, this problem occurs when you have two even numbers after each other in the list and you remove from the same list as you are looping over

1 Like

#Write your function here
def delete_starting_evens(lst):
while (len(lst) > 0 and lst[0] % 2 == 0):
lst.pop(0)
return lst

def dse(lst):
for i in lst:
if len(lst) > 0 and lst[0] % 2 == 0:
lst.pop(0)
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(dse([4, 8, 10]))

I tried it with the for loop in function defined as dse() and it does not pop out the 10 of the list, could anyone explain why is that? thank you

1 Like

When i is a positional element and we pop that element, all the elements to the right of that get shifted. The next element to be iterated in the list will be from one more to the right that it was expected to be, hence an element is skipped.

4, 8, 10    # i is 4
pop(0)
8, 10       # i is 10
pop(0)
10          # i is None

The return value will be [10].

1 Like

The first solution that I have put #'s before works.
Why does the second solution leave [10] from a list of [4, 8, 10]? Why doesn’t it remove the 10 as well and give me ?

bank = [4]
bank.pop(0)
print(bank)
#the above gives me , so why does .pop(0) in a loop leave one number in the list?

2 Likes

looping over the same list as you are removing from is problematic.

lists can’t have empty spaces/slots, so the empty slot which was caused by the removal of an element is filled, while the loop continues to the next index, causing a value to be skipped

1 Like

Is there an order to which the values are skipped when this happens?

1 Like

The iterator moves forwards, same as ever.
You’re moving the values.

Also, you probably don’t want to pop at the front of the list. Do it at the back instead so that you don’t need to move all the other values every time.
(similarly you’re not going to want to copy all-but-the-first every iteration, better to find out where to slice, and then do it once)

…Except that there’s no need to modify the original, the simpler approach is to create a new list with the values to keep.

1 Like

Hello @stetim94,

I guess a lot of people have issues with the ways to solve this exercise. I have used all methods to test my knowledge and all worked: lst[1:], del, lst.remove(), but can not get my peace with .pop() method in for loop as the previous user had issue (it worked in while loop!).

It works in the following code:

def delete_starting_evens(lst):
  for number in lst:
    if number % 2 == 0:
      lst.pop(0)
    else:
      break
  return lst
print(delete_starting_evens([4, 8, 10, 11, 12, 15]))
# prints: [11, 12, 15]

but here doesn’t return an empty list as it supposed to:

print(delete_starting_evens([4, 8, 10]))
# prints: [10]

Why it can not .pop()out that last element?

Thank you for your “Why”!

1 Like

A for loop advances to the next element in the list, but if we remove (pop) the current element, the next one takes its place and the loop advances still to the next element.

Try using while.

while lst[0] % 2 == 0:
    lst.pop(0)
return lst
2 Likes

Thank you @mtf for your input - I have used while loop earlier and it worked for me. I was just puzzled why .pop() works in one instance and does not work in another (I would be OK to know if it would not work at all, but it does in the first lst example). When you are a beginner, such things just fry your brain :wink:

1 Like

The key is to keep note of the pointer of a for loop. Think in terms of next being loaded with the index following when reaching the current element, and it does not change when the list is shifted to the left.

1 Like

Thank you @mtf for your time! So according to this logic, what .pop() is trying to do in this instance of for loop:

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

when loop tries to advance to the next item after 10, it sees that there’s no further item in the lst, so it can not actually .pop() out 10 because 10 has not been assigned 0 index?

Thank you!

1 Like