2010-04-07 88 views
10

我正在尝试创建一个在集合操作中行为正常的自定义对象。集合操作中对象的行为

我已经普遍认识到它的工作原理,但是我想确保我完全理解它的含义。特别是,当对象中有其他数据未包含在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 

这种行为的地方,并确定记录?如果是这样,那么治理原则是什么?

回答

4

不,它不是确定性的。问题在于你已经破坏了equals和hash的不变量,当它们相等时,两个对象是等价的。修复你的对象,不要试图聪明,滥用set的实现工作。如果元值是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] 
+0

望着文档的__hash__方法,它不会出现,表明不可能有未散列对象中的数据。我可以想到许多例子,其中两个相同的对象将具有不同的某种形式的元数据(可能是时间戳或文件名)。 从__hash__的文档:唯一需要的属性是比较相等的对象具有相同的散列值;建议以某种方式混合在一起(例如,使用排他或散列值)用于对象的组成部分,这些组成部分也是对象比较的一部分。 – 2010-04-07 19:10:33

+1

我对你的评论感到困惑,你似乎同意我的看法。如果一个对象具有被eq和hash忽略的元数据(例如时间戳或文件名),那么它们不够重要,不能被保存,要么被比较的对象会做。如果它们足够重要以区分这两个对象,它们将被包含在散列和等式中。你现在在问什么? – hlfrk414 2010-04-07 19:22:33

+0

我并不赞同你;)。试图了解如何使用这些功能。在这种情况下,我有一个由监视代理创建的对象。尝试关联具有不同时间戳的重复性警报条件。宁愿保留旧的对象,但当然我可以用其他方式实现它,因为我怀疑你是正确的。 – 2010-04-07 19:46:40

1

订单似乎并不重要:

>>> [ (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

比方说,你的对象有两种不同类型的属性:关键属性和数据属性。在你的例子中,MyObject.value属性。

将所有对象存储为字典中的值,并以属性为关键字,确保只有您的首选(例如具有最早的时间戳)输入字典中。与在词典中使用相同的密钥进行您所设定的操作,和检索字典中的实际对象:

result= [dict1[k] for k in set_operation_result]