Quilty Kim
Quilty Kim

Reputation: 463

issue with removing item from list in python

In the method below, I have assembled a list of tuples, while trying to ensure that none of the values are less than zero. The method below first takes a block and uses it to calculate the coordinates of neighboring blocks in increments of one and then proceeds to remove blocks that have either of their coordinates less than zero. My issue lies in the second stage as it does not remove two coordinates if I input a block with coordinates (0,0): (0,-1) and (-1,0).

The codes is as follows:

def get_block_neighbors(self, block):
    neighbor_list = []
    neighbor_list.append((block.x,block.y+1))
    neighbor_list.append((block.x+1,block.y+1))
    neighbor_list.append((block.x+1,block.y))
    neighbor_list.append((block.x+1,block.y-1))
    neighbor_list.append((block.x,block.y-1))
    neighbor_list.append((block.x-1,block.y-1))
    neighbor_list.append((block.x-1,block.y))
    neighbor_list.append((block.x-1,block.y+1))

    for item in neighbor_list: #each tuple item in the list
        if item[0] < 0 or item[1] < 0:
            print item
            neighbor_list.remove(item)
    print neighbor_list

get_block_neighbors(Block(Point(0,0),"green"))

for which I get the following output:

(1, -1)
(-1, -1)
(-1, 1)
[(0, 1), (1, 1), (1, 0), (0, -1), (-1, 0)]

Here, the first three lines are printouts of the tuples I would like to remove, while the last one is a list of the tuples that I have assembled. As you can see, the last two tuples have negative values for at least one of their coordinates. Ideally, I would want this:

(1, -1)
(-1, -1)
(-1, 1)
(0, -1)
(-1, 0)
[(0, 1), (1, 1), (1, 0)] 

Curiously enough, when I remove/comment out the line neighbor_list.remove(item), I get a bit closer to where I need to be in one sense in that the method in its print-out includes the two tuples that I want removed. But of course, the one disadvantage of doing this is that I no longer remove any of the target tuples from this list.

Any help on this would be much appreciated and I really do hope that my oversight wasn't something super obvious. On a side note, I feel like there should be a way to assemble this list while being able to forego a removal stage, which is how I started coding this method before I just settled for the code above. Thanks!

Upvotes: 2

Views: 428

Answers (6)

John La Rooy
John La Rooy

Reputation: 304147

Here is an example, showing some code that looks like it's trying to filter some numbers from a list

>>> L = range(10)
>>> for x in L:
...     print x, L
...     if x in (4,5,6,7):
...         L.remove(x)
... 
0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
3 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
4 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
6 [0, 1, 2, 3, 5, 6, 7, 8, 9]
8 [0, 1, 2, 3, 5, 7, 8, 9]
9 [0, 1, 2, 3, 5, 7, 8, 9]

Removing items while iterating over the list is generally a bad idea.

Here is a simpler way to get the list of neighbors. It avoids the need for .remove altogether

def get_block_neighbors(self, block):
    x = block.x
    y = block.y
    xrange = (-1, 0, 1)[x<1:]
    yrange = (-1, 0, 1)[y<1:]
    return [(x + dx,y + dy) for dx in xrange for dy in yrange if dx or dy]

Upvotes: 0

Christian Ternus
Christian Ternus

Reputation: 8492

Much easier all around:

def get_block_neighbors(self, block):
    neighbor_list = []
    neighbor_list.append((block.x,block.y+1))
    neighbor_list.append((block.x+1,block.y+1))
    neighbor_list.append((block.x+1,block.y))
    if block.y > 0:
        neighbor_list.append((block.x+1,block.y-1))
        neighbor_list.append((block.x,block.y-1))
        if block.x > 0:
            neighbor_list.append((block.x-1,block.y-1))
    if block.x > 0:
        neighbor_list.append((block.x-1,block.y))
        neighbor_list.append((block.x-1,block.y+1))

    return neighbor_list

Upvotes: 0

Blckknght
Blckknght

Reputation: 104712

The issue is that you're removing items from the list at the same time you're iterating over that list. List iteration happens by index (behind the scenes) so this doesn't work as you'd expect, as some values are skipped when their predecessors are removed.

To avoid this issue, you can iterate over a copy of the list, using a slice:

for item in neighbor_list[:]:

Or, better yet, use a list comprehension to build a new list instead of modifying the old one:

new_list = [(x, y) for x, y in neighbor_list if x >= 0 and y >= 0]

Upvotes: 3

Christian Tapia
Christian Tapia

Reputation: 34146

Make a copy of the list first:

for item in neighbor_list[:]: #each tuple item in the list
    if item[0] < 0 or item[1] < 0:
        print item
        neighbor_list.remove(item)
print neighbor_list

Upvotes: 1

&#211;scar L&#243;pez
&#211;scar L&#243;pez

Reputation: 236004

The problem lies in the fact that you're removing elements of a list and at the same time you're iterating over it. It's simpler if you just create a new list with the elements removed:

[item for item in neighbor_list if item[0] >= 0 and item[1] >= 0]

Upvotes: 1

Crowman
Crowman

Reputation: 25908

You shouldn't remove items from a list you're iterating over. Make a copy of the list first, and iterate over that instead.

Upvotes: 1

Related Questions