4

I have a list in the given format:

[['John', 'Smith'], ['Linus', 'Torvalds'], ['Bart', 'Simpson']]

There are some elements like this in the list ['Linus Torvalds', ''] and I want to remove those. So why doesn't the following code remove them?

for i in people:
    if(i[0] == '' or i[1] == ''):
        print people.pop(people.index(i))
Diego Allen
  • 4,623
  • 5
  • 30
  • 33
  • I've also tried people.remove(i) – Diego Allen Jul 06 '11 at 15:07
  • 3
    The list you gave doesn't have any cases where either the first or second element is an empty string. – Jeff Ames Jul 06 '11 at 15:09
  • When you want to deal with the index as well, use something like `for i, (first, last) in enumerate(people):` and `i` will be 0, 1, 2, ... – Chris Morgan Jul 06 '11 at 15:12
  • It's quite possible to do what you're trying to do with getting the iterator (`iter(people)`) and calling `iterator.next()` only some of the time... but that's rarely the best solution. – Chris Morgan Jul 06 '11 at 15:17

4 Answers4

10

You are changing the list while iterating over it and this is the source of your problems. An approach that works is

people[:] = [p for p in people if p[0] != '' and p[1] != '']

this way a new temporary list containing only the elements you want is built and then assigned to the original list object when the operation is complete.

6502
  • 112,025
  • 15
  • 165
  • 265
  • 2
    if you want to end up with the same *list object*, you can replace the assignment with `people[:] = [p for p in ...`, that way, the result of the list comprehension gets stuffed back into the original list. – SingleNegationElimination Jul 06 '11 at 15:15
  • @TokenMacGuy: Good point. Updated the answer. – 6502 Jul 06 '11 at 16:13
  • 1
    Hmmm. now nobody can see the old way, when you don't need to preserve the list object identity, you can replace the assignment with just `people = [p for p in ...`, which is going to be a bit faster since it avoids some (but not terribly much) copying. – SingleNegationElimination Jul 06 '11 at 19:09
  • The original code was trying to change the object in place, using `pop`, possibly because others have an access to it; the current version is therefore a better answer to the original question from a semantic point of view. – 6502 Jul 06 '11 at 21:35
  • It may be closer to his original solution, but that doesn't mean it's the best solution for any problem that is similar to the question, even for this particular poster. A successful pythonista can use both techniques. – SingleNegationElimination Jul 06 '11 at 22:19
4

Or even people[:] = [p for p in people if all(p)] if you want to resize the list "in place".

Matt Joiner
  • 112,946
  • 110
  • 377
  • 526
3

You're modifying the list's length while iterating over it. That causes you to skip values. When you pop one item off the list, here's what happens (stealing from this answer):

[1, 2, 3, 4, 5, 6...]
 ^

That's the state of the list initially; now say 1 is removed and the loop goes to the second item in the list:

[2, 3, 4, 5, 6...]
    ^

And so on.

Community
  • 1
  • 1
senderle
  • 145,869
  • 36
  • 209
  • 233
1

It's a bad idea to remove things from a list as you iterate over it. So, try one of these instead (Also, I think your condition is not what you want it to be - I've fixed it):

L = [['John', 'Smith'], ['Linus', 'Torvalds'], ['Bart', 'Simpson']]
delete_these = []
for index, i in enumerate(L):
    if not i[-1].strip():
        delete_these.append(i)
for i in delete_these:
    L.pop(i)
    delete_these = map(lambda x: x-1, delete_these)

OR

L = [i for i in L if i[-1].strip()]

OR

answer = []
for i in L:
    if i[-1].strip():
        answer.append(i)

OR

i = 0
while i < len(L):
    if not L[i][-1].strip():
        L.pop(i)
    else:
        i += 1

Hope this helps

inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241