0

I am wondering what is the reason why some elements weren't removed in this program. Could someone provide pointers?

Program:

t = ['1', '2', '2', '2', '2', '2', '2', '2', '2', '2', '7', '8', '9', '10']

print len(t)

for x in t:
    if x == '2':
            print x
            t.remove(x)
    else:
        print 'hello: '+str(x)

print t

Output on my system:

14
hello: 1
2
2
2
2
2
hello: 8
hello: 9
hello: 10
['1', '2', '2', '2', '2', '7', '8', '9', '10']

I am using Python 2.6.2.

Kijewski
  • 25,517
  • 12
  • 101
  • 143
  • 5
    The problem here is that you're modifying the list while iterating over it, which is a no-no. – jme Nov 16 '15 at 00:24
  • 1
    Iterate backwards, or read http://stackoverflow.com/questions/10665591/how-to-remove-list-elements-in-a-for-loop-in-python. – jarmod Nov 16 '15 at 00:27
  • Are you still wanting to know _why_ the elements are skipped, or have you worked it out now? – John La Rooy Nov 16 '15 at 03:43

3 Answers3

3

Never alter the sequence on which you're iterating.

@cjonhson318's list-comprehension will work fine, or, less efficiently but more closely akin to your code, just loop on a copy of the list while you're altering the list itself:

for x in list(t):
    if x == '2':
            print x
            t.remove(x)
    else:
        print 'hello: '+str(x)

As you see the only change from your code is looping on list(t) (a copy of t's initial value) rather than on t itself -- this modest change lets you alter t itself within the loop to your heart's contents.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • Thanks. It looks like I have missed some Python Internals here on how it implemented looping over the list and impact of removal of it's items while looping over it. At this moment, I understand I shouldn't modify the the list while iterating over it. However, if there are any Python Internals concepts in understanding this deeper would be very helpful. Thanks again. – Bharath Nov 16 '15 at 17:23
  • @Bharath, no deep internals involved: very simply, looping on a sequence (or a dict or a set) does NOT imply making a copy of it (which would take time and memory) so you can't modify the structure of the underlying container while you're looping -- if you DO want to modify structure as you loop (and, of course, therefore inevitably accept the time/memory costs thereof), just explicitly take a copy yourself. "Explicit is better than implicit"!-) – Alex Martelli Nov 16 '15 at 17:35
1

Say something like:

t = [ i for i in t if i != '2' ]
for item in t:
    print "Hello "+item
cjohnson318
  • 3,154
  • 30
  • 33
1

An alternative would be to get functional

from operator import ne
from functools import partial

t = ['1', '2', '2', '2', '2', '2', '2', '2', '2', '2', '7', '8', '9', '10']

for n in filter(partial(ne, '2'), t):
    print('hello {}'.format(n))

Use the filter function to create a new list minus the 2 values.

If the use of partial and operator.ne was not to your liking, you could use a lambda

for n in filter(lambda x: x != '2', t):
    print('hello {}'.format(n))
Paul Rooney
  • 20,879
  • 9
  • 40
  • 61