2

I have a list in python and every time an element satisfies a certain condition I remove the element. The problem is that the for cycle seems to be skipping some elements. I think it's because the list is moved to the left after a delete. So how to properly remove items in a list? This is my code

list = [0, 0, 0, 1, 1, 0 ,0, 1, 0]

for elem in list:
    if elem == 0:
        list.remove(elem)

print(list)

and this is the result [1, 1, 0, 1, 0]

JayJona
  • 469
  • 1
  • 16
  • 41
  • 4
    in Python better create new list with elements which you want to keep. – furas Nov 20 '19 at 19:34
  • 4
    Never remove elements from the list you're iterating over. Create a new list. – David Buck Nov 20 '19 at 19:34
  • 1
    Also, don't use `list` as a variable name as you may cause problems for yourself as you've redefined `list` which is a python keyword. If you've seen the error `TypeError: 'list' object is not callable`, it's because Python has tried to execute your list called `list` rather than its own object. – David Buck Nov 20 '19 at 19:45

6 Answers6

11

You shouldn't delete or add elements to a list whilst iterating over it. Here are some alternatives.

Method 1: Create a new filtered list.

[x for x in my_list if x != 0]

Method 2: If the items are booleans, you can use filter.

list(filter(lambda x: x, my_list))  # or lambda x: x != 0 depending on intent.

Method 3: Create an index of items to be deleted and then subsequently remove them.

idx = [n for n, x in enumerate(my_list) if x == 0]
for i in reversed(idx):
    my_list.pop(i)

Method 4: Per the solution of Alex Martelli in the related question

my_list[:] = [x for x in my_list if x != 0]

This assigns to a list slice that matches the my_list variable name, thereby preserving any existing references to this variable.

Alexander
  • 105,104
  • 32
  • 201
  • 196
  • 2
    You can go even further and skip "!= 0" because `0` evaluates to `False` when "if'ed" – Damian Nov 20 '19 at 19:36
  • 1
    Yes, possibly. Given the booleans in the example above that is True. However, if one wants to differentiate between zeros and other falsies that may not be the case. Method 2 filters as falsies. – Alexander Nov 20 '19 at 19:38
  • Yea, of course, good point :) I'm just telling about this particular scenario as it looks for me as list with binary values but in other case this might be bad decision. – Damian Nov 20 '19 at 19:41
2

use filter

lst = [0, 0, 0, 1, 1, 0 ,0, 1, 0]

lst = list(filter(lambda elem: elem != 0, lst))

print(lst)

also avoid defining the term list as this is already being used by the built in list type, otherwise you'll prevent yourself from using terms like list(...) inside of this scope.

alternatively you can use a list comprehension

lst = [0, 0, 0, 1, 1, 0 ,0, 1, 0]

lst = [elem for elem in lst if elem != 0]

print(lst)
Dash Winterson
  • 1,197
  • 8
  • 19
2

You can't use .remove() because that removes the first occurrence of the given value, but you need to remove items by their position in the list.

Also you'll want to iterate the positions in reverse order, so removing an item doesn't shift the positions of the remaining (earlier) list items.

for position in reversed(range(len(mylist))):
    if mylist[position] == 0:
        del mylist[position]
John Gordon
  • 29,573
  • 7
  • 33
  • 58
  • since I have simplified the list to replicate the error, the solution of scrolling the list in reverse order could be useful to avoid shifting problems, can you show me the code to scroll the list in reverse order? – JayJona Nov 20 '19 at 19:42
  • 1
    See my updated answer. – John Gordon Nov 20 '19 at 20:17
1

There are more 'pythonic' ways to solve this problem using a lambda function. But to solve this using just looping it becomes easier to understand:

list = [0, 0, 0, 1, 1, 0 ,0, 1, 0]

i = 0
length = len(list)
while (i < len(list)):
    if list[i] == 0:
        list.remove(list[i])
        i -= 1
        length -= 1
    else:
        i += 1
print(list)

By decreasing i by 1 inside the if statement we can ensure we don't miss any elements. Also to prevent out of bounds looping we must also decrease the length used in the while condition.

Jamie
  • 171
  • 2
0

You can use this if you always want to remove 0's from your list:

[elem for elem in list if elem]

This obviously gives you a new list without making changes to your existing list.

Soroush Sotoudeh
  • 198
  • 3
  • 17
-1

As you said, when the item is deleted, the list moves. Since the for loop uses the index within the loop, it gets the index wrong and the wrong item gets removed.

A simple way around this, if you don't wish to rewrite a lot of code is to traverse the list backwards

list = [0, 0, 0, 1, 1, 0 ,0, 1, 0]

for elem in reversed(list):
    if elem == 0:
        list.remove(elem)

print(list)
cup
  • 7,589
  • 4
  • 19
  • 42
  • 1
    this solved my problem, as I wrote in a comment I needed to scroll the list in reverse. Thanks to all by the way – JayJona Nov 20 '19 at 20:02
  • Note that this method is inefficient. Although you are traversing the list in reverse order, `.remove(elem)` actually removes the _first_ element in the list, so that it would become `[0, 0, 1, 1, 0 ,0, 1, 0]` after the first pass. This means that the entire list needs to be shifted over, which is an inefficient operation. – Alexander Nov 21 '19 at 17:37
  • Yes, I know it is inefficient but if the Op has written a lot of code between the for and the if, then this is the method with the least changes. If, however, there is no code in between then one of the other methods posted would be better. – cup Nov 21 '19 at 19:17
  • Are we sure this doesn't avoid the problem by just creating a new instance of the list returned by `reversed`? – Dash Winterson Nov 21 '19 at 21:12
  • If you are indexing into a list and delete from the back,, the indices of the items before the deleted one will always remain the same. – cup Nov 22 '19 at 05:26