Поведение объекта в заданных операциях - PullRequest
10 голосов
/ 07 апреля 2010

Я пытаюсь создать пользовательский объект, который ведет себя правильно в операциях над множествами.

У меня, как правило, все работает, но я хочу убедиться, что я полностью понимаю последствия. В частности, меня интересует поведение, когда в объекте есть дополнительные данные, которые не включены в методы equal / hash. Похоже, что в операции «пересечение» он возвращает набор сравниваемых объектов, а операция «объединение» возвращает набор сравниваемых объектов.

Для иллюстрации:

class MyObject:
    def __init__(self,value,meta):
        self.value = value
        self.meta = meta
    def __eq__(self,other):
        return self.value == other.value
    def __hash__(self):
        return hash(self.value)

a = MyObject('1','left')
b = MyObject('1','right')
c = MyObject('2','left')
d = MyObject('2','right')
e = MyObject('3','left')
print a == b # True
print a == c # False

for i in set([a,c,e]).intersection(set([b,d])):
    print "%s %s" % (i.value,i.meta)
#returns:
#1 right
#2 right

 for i in set([a,c,e]).union(set([b,d])):
    print "%s %s" % (i.value,i.meta)
#returns:
#1 left
#3 left
#2 left

Задокументировано ли это поведение и является ли оно детерминированным? Если да, то каков руководящий принцип?

Ответы [ 3 ]

4 голосов
/ 07 апреля 2010

Нет, это не детерминистично. Проблема в том, что вы нарушили инвариант equals и hash, что два объекта эквивалентны, когда они равны. Исправьте свой объект, не пытайтесь быть умным и злоупотребляйте реализацией набора. Если мета-значение является частью идентификатора MyObject, его следует включить в eq и hash.

Вы не можете полагаться на пересечение множества, чтобы следовать любому порядку, поэтому нет способа легко сделать то, что вы хотите. В конечном итоге вы берете пересечение только по значению, а затем просматриваете все свои объекты, чтобы найти более старый, чтобы заменить его для каждого. Нет хорошего способа сделать это алгоритмически.

Союзы не так уж и плохи:

##fix the eq and hash to work correctly
class MyObject:
    def __init__(self,value,meta):
        self.value = value
        self.meta = meta
    def __eq__(self,other):
        return self.value, self.meta == other.value, other.meta
    def __hash__(self):
        return hash((self.value, self.meta))
    def __repr__(self):
        return "%s %s" % (self.value,self.meta)

a = MyObject('1','left')
b = MyObject('1','right')
c = MyObject('2','left')
d = MyObject('2','right')
e = MyObject('3','left')

union =  set([a,c,e]).union(set([b,d]))
print union
#set([2 left, 2 right, 1 left, 3 left, 1 right])

##sort the objects, so that older objs come before the newer equivalents
sl = sorted(union, key= lambda x: (x.value, x.meta) )
print sl
#[1 left, 1 right, 2 left, 2 right, 3 left]
import itertools
##group the objects by value, groupby needs the objs to be in order to do this
filtered = itertools.groupby(sl, lambda x: x.value)
##make a list of the oldest (first in group)
oldest = [ next(group) for key, group in filtered]
print oldest
#[1 left, 2 left, 3 left]
1 голос
/ 07 апреля 2010

Порядок не имеет значения:

>>> [ (u.value, u.meta) for u in set([b,d]).intersection(set([a,c,e])) ]
[('1', 'right'), ('2', 'right')]

>>> [ (u.value, u.meta) for u in set([a,c,e]).intersection(set([b,d])) ]
[('1', 'right'), ('2', 'right')]

Однако, если вы сделаете это:

>>> f = MyObject('3', 'right')

И добавьте f к «правильному» набору:

>>> [ (u.value, u.meta) for u in set([a,c,e]).intersection(set([b,d,f])) ]
[('1', 'right'), ('3', 'right'), ('2', 'right')]

>>> [ (u.value, u.meta) for u in set([b,d,f]).intersection(set([a,c,e])) ]
[('1', 'left'), ('3', 'left'), ('2', 'left')]

Итак, вы можете видеть, что поведение зависит от размера наборов (тот же эффект происходит, если вы union).Это может зависеть и от других факторов.Я думаю, что вы ищете источник Python, если хотите знать почему.

0 голосов
/ 08 апреля 2010

Допустим, у ваших объектов есть два разных типа атрибутов: ключ атрибуты и данные атрибуты.В вашем примере MyObject.value является атрибутом key .

Сохраните все свои объекты в виде значений в словаре, используя ключевые атрибуты key , убедившись, что в словаре введены только ваши предпочтительные (например, с самой старой отметкой времени)Выполните операции над множествами с тем же ключом, который используется в словаре, и извлеките фактические объекты из словаря:

result= [dict1[k] for k in set_operation_result]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...