Matg0d
Matg0d

Reputation: 29

Make a copy of a set and exclude one item

Im trying to make a set based in another set, and exclude only one item... (do a for loop inside another for loop with an object that is inside a set, but not iterate with itself on the second loop)

Code:

for animal in self.animals:
    self.exclude_animal = set((animal,))
    self.new_animals = set(self.animals)
    self.new_animals.discard(self.exclude_animal) # parameters for a set needs to be a set?

    for other_animal in (self.new_animals):
        if animal.pos[0] == other_animal.pos[0]:
            if animal.pos[1] == other_animal.pos[1]:
                self.animals_to_die.add(animal)
                print other_animal
                print animal
                self.animals_to_die.add(other_animal)

Point is, my print statement returns the object id(x), so I know that they are the same object, but they should not be, I discard it on that set new_animals set.

Any insight in why this doesn't exclude the item?

Upvotes: 2

Views: 2760

Answers (2)

Duncan
Duncan

Reputation: 95712

As you mark both animals to die, you don't need to compare A to B and also B to A (which your current code does). You can ensure you get only unique pairs of animals by using itertools.combinations():

for animal, other_animal in itertools.combinations(self.animals, 2):
    if animal.pos==other_animal.pos:
        self.animals_to_die.update([animal, other_animal])

Just for fun, I'll point out you can even do it as a single expression (though I think it reads better as an explicit loop):

self.animals_to_die.update(sum(((animal,other_animal)
    for animal,other_animal in itertools.combinations(self.animals, 2)
    if animal.pos == other_animal.pos), ()))

For clarity, itertools.combinations() gives you all the unique combinations of its input. The second argument controls how many elements are selected each time:

>>> list(itertools.combinations(["A", "B", "C", "D"], 2))
[('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
>>> list(itertools.combinations(["A", "B", "C", "D"], 3))
[('A', 'B', 'C'), ('A', 'B', 'D'), ('A', 'C', 'D'), ('B', 'C', 'D')]

That works well in this case as the code given appears to be symmetric (two animals on the same location mutually annihilate each other). If it had been asymmetric (one kills the other) then you would have to remember to test both ways round on each iteration.

Upvotes: 1

Martijn Pieters
Martijn Pieters

Reputation: 1123420

set.discard() removes one item from the set, but you pass in a whole set object.

You need to remove the element itself, not another set with the element inside:

self.new_animals.discard(animal)

Demo:

>>> someset = {1, 2, 3}
>>> someset.discard({1})
>>> someset.discard(2)
>>> someset
set([1, 3])

Note how 2 was removed, but 1 remained in the set.

It would be easier to just loop over the set difference here:

for animal in self.animals:    
    for other_animal in set(self.animals).difference([animal]):
        if animal.pos == other_animal.pos:
            self.animals_to_die.add(animal)
            print other_animal
            print animal
            self.animals_to_die.add(other_animal)

(where I assume that .pos is a tuple of two values, you can just test for direct equality here).

You don't need to store new_animals on self all the time; using local names suffices and is not even needed here.

Upvotes: 6

Related Questions