0

As a beginner, I am writing a simple script to better acquaint myself with python. I ran the code below and I am not getting the expected output. I think the for-loop ends before the last iteration and I don't know why.

letters = ['a', 'b', 'c', 'c', 'c'] 
print(letters)
for item in letters:
    if item != 'c':
        print('not c')
    else:
        letters.remove(item)
        continue
print(letters)

output returned:

['a', 'b', 'c', 'c', 'c'] 
not c 
not c
['a', 'b', 'c']

Expected Output:

['a', 'b', 'c', 'c', 'c'] 
not c 
not c
['a', 'b']

Basically, I am not expecting to have 'c' within my list anymore. If you have a better way to write the code that would be appreciated as well.

TheoretiCAL
  • 19,461
  • 8
  • 43
  • 65
Biyi Momoh
  • 13
  • 3

2 Answers2

2

WARNING: This is an inefficient solution that I will provide to answer your question. I'll post a more concise and faster solution in answer #2.

Answer #1

When you are removing items like this, it changes the length of the list, so it is better to loop backwards. Try for item in letters[::-1] to reverse the list:

letters = ['a', 'b', 'c', 'c', 'c'] 
print(letters)
for item in letters[::-1]:
    if item != 'c':
        print('not c')
    else:
        letters.remove(item)
        continue
print(letters)

output:

['a', 'b', 'c', 'c', 'c']
not c
not c
['a', 'b']

Answer #2 - Use list comprehension instead of looping (more detail: Is there a simple way to delete a list element by value?):

letters = ['a', 'b', 'c', 'c', 'c']
letters = [x for x in letters if x != 'c']

output:

['a', 'b']
David Erickson
  • 16,433
  • 2
  • 19
  • 35
  • This is generally not the preferred way of doing this, because this is a worst-case quadratic time algorithm – juanpa.arrivillaga Jul 15 '20 at 01:15
  • Thansk @juanpa.arrivillaga . This is worst case in terms of time to execute? – David Erickson Jul 15 '20 at 01:18
  • Yes. Using `.remove` like that is inefficient, it is a linear time operation. In a loop it becomes quadratic time overall – juanpa.arrivillaga Jul 15 '20 at 01:32
  • gotchya @juanpa.arrivillaga , I would never personally write code like this, but this was my answer without overhauling the OP's code completely and to help answer the OP's specific question. This may just be education for the OP, or the OP is dealing with tiny lists, where this code would be acceptable. I mostly use pandas and used to loop as a beginner, but try to avoid looping at all costs, especially in expensive ways. I'll update my answer to give light to your warning. – David Erickson Jul 15 '20 at 01:36
  • 1
    My point is if you are using loops and lists *you still wouldn't do it this way*. You would loop over and create a new list, like your list comprehension. Avoiding loops isn't really a programming best practice anyway, that's something that is sepcific to pandas/numpy (because the whole point is to leverage *built-in* loops in compiled code) – juanpa.arrivillaga Jul 15 '20 at 01:50
0

the letters.remove(item) removes only a single instance of the element, but has the unintentional effect of reducing the size of the list as you are iterating over it. This is something you want to generally avoiding doing, modifying the same element you are iterating over. As a result the list becomes shorter and the iterator believes you have traversed all of the elements, even though the last 'c' is still in the list. This is seen with the output of:

letters = ['a', 'b', 'c', 'c', 'c'] 
print(letters)
for idx,item in enumerate(letters):
    print("Index: {} Len: {}".format(idx,len(letters)))
    if item != 'c':
        print('not c')
    else:
        letters.remove(item)
        continue
print(letters)

"""Index: 0 Len: 5
not c
Index: 1 Len: 5
not c
Index: 2 Len: 5
Index: 3 Len: 4"""

You never iterate over the last element because the index (4) would exceed the indexable elements of the list (0-3 now)!

If you want to filter a list you can use the built in filter function:

filter(lambda x: x!='c', letters)
TheoretiCAL
  • 19,461
  • 8
  • 43
  • 65