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

(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].

2 Likes

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

because it is at the next index on the right of 10, with 10 still being in list.

v
4  8  10

Once 4 is in play, the pointer advances to be ready for next.

   v
4  8  10

now we pop 4

    v
 8  10

Now 8 is in play and the pointer advances

        v
 8  10

The value in the iterator is 10, which is even, but 8 is the one that gets popped. There is no next so the loop terminates, leaving the 10 still in the list.

5 Likes

Thank you @mtf - visual now helped to clear the tangled-up logic in my mind, thank you for your time and patience!

2 Likes