Python2.7 - List.remove(item) Within A Loop Gives Unexpected Behaviuor
Solution 1:
It should be like this
for i in lst[:]:
if i % 2 == 0:
print i
lst.remove(i)
print lst
Problem:
You are modifying the list while you iterate over it. Due to which the iteration is stopped before it could complete
Answer :
You could iterate over copy of the list
You could use list comprehension
:
lst=[i for i in lst if i%2 != 0]
Solution 2:
By using list.remove
, you are modifying the list during the iteration. This breaks the iteration giving you unexpected results.
One solution is to create a new list using either filter
or a list comprehension:
>>>filter(lambda i: i % 2 != 0, lst)
[5, 5]
>>>[i for i in lst if i % 2 != 0]
[5, 5]
You can assign either expression to lst
if needed, but you can't avoid creating a new list object with these methods.
Solution 3:
Other answers have already mentioned that you're modifying the list while iterating over it, and offered better ways to do it. Personally I prefer the list comprehension method:
odd_numbers = [item for item in numbers if item % 2 != 0]
For your specified case of a very small list, I would definitely go with that.
However, this does create a new list, which could be a problem if you have a very large list. In the case of integers, large probably means millions at least, but to be precise, it's however large it needs to be to start giving you issues with memory usage. In that case, here are a couple ways to do it.
One way is similar to the intent of the code in your question. You iterate over the list, removing the even numbers as you go. However, to avoid the problems that modifying a list you're iterating over can cause, you iterate over it backwards. There are ways to iterate forward, but this is simpler.
Here's one way using a while
loop:
# A one hundred million item list that we don't want to copy# even just the odd numbers from to put into a new list.
numbers = range(100000000) # list(range(100000000)) in Python 3index = len(numbers) - 1# Start on the index of the last itemwhileindex >= 0:
if numbers[index] % 2 == 0:
numbers.pop(index)
index -= 1
Here's another way using a for
loop:
# A one hundred million item list that we don't want to copy# even just the odd numbers from to put into a new list.
numbers = range(100000000) # list(range(100000000)) in Python 3forindex in xrange(len(numbers) - 1, -1, -1): # range(...) in Python 3if numbers[index] % 2 == 0:
numbers.pop(index)
Notice in both the while
loop and for
loop versions, I used numbers.pop(index)
, not numbers.remove(numbers[index])
. First of all, .pop()
is much more efficient because it provides the index, whereas .remove()
would have to search the list for the first occurrence of the value. Second, notice that I said, "first occurrence of the value". That means that unless every item is unique, using .remove()
would remove a different item than the one the loop is currently on, which would end up leaving the current item in the list.
There's one more solution I want to mention, for situations where you need to keep the original list, but don't want to use too much more memory to store a copy of the odd numbers. If you only want to iterate over the odd numbers once (or you're so averse to using memory that you'd rather recalculate things when you need to), you can use a generator. Doing so would let you iterate over the odd numbers in the list without needing any additional memory, apart from the inconsequential amount used by the generator mechanism.
A generator expression is defined exactly like a list comprehension, except that it's enclosed in parentheses instead of square brackets:
odd_numbers = (item for item in numbers if item % 2 != 0)
Remember that the generator expression is iterating over the original list, so changing the original list mid-iteration will give you the same problems as modifying a list while iterating over it in a for
loop. In fact, the generator expression is itself using a for
loop.
As an aside, generator expressions shouldn't be relegated only to very large lists; I use them whenever I don't need to calculate a whole list in one go.
Summary / TLDR:
The "best" way depends exactly what you're doing, but this should cover a lot of situations.
Assuming lists are either "small" or "large":
If your list is small, use the list comprehension (or even the generator expression if you can). If it's large, read on.
If you don't need the original list, use the while
loop or for
loop methods to remove the even numbers entirely (though using .pop()
, not .remove()
). If you do need the original list, read on.
If you're only iterating over the odd numbers once, use the generator expression. If you're iterating over them more than once, but you're willing to repeat computation to save memory, use the generator expression.
If you're iterating over the odd numbers too many times to recompute them each time, or you need random access, then use a list comprehension to make a new list with only the odd numbers in them. It's going to use a lot of memory, but them's the breaks.
Solution 4:
As a general principle, you should not modify a collection while you are iterating over it. This leads to skipping of some elements, and index error in some cases.
Instead of removing elements from list, it would be easier if you just create another reference with same name. It has lesser time complexity too.
lst = filter(lambda i: i % 2 !=0, lst)
Post a Comment for "Python2.7 - List.remove(item) Within A Loop Gives Unexpected Behaviuor"