5

因此,有一种简单的方法可以通过 set.intersection() 计算两个集合的交集。但是,我有以下问题:

class Person(Object):                    
    def __init__(self, name, age):                                                      
        self.name = name                                                                
        self.age = age                                                                  

l1 = [Person("Foo", 21), Person("Bar", 22)]                                             
l2 = [Person("Foo", 21), Person("Bar", 24)]                                             

union_list = list(set(l1).union(l2))                                           
# [Person("Foo", 21), Person("Bar", 22), Person("Bar", 24)]

Object是我的 ORM 提供的一个基类,它实现了基本__hash____eq__功能,它基本上将类的每个成员都添加到哈希中。换句话说,__hash__返回的将是类的每个元素的哈希)

在这个阶段,我想只运行一个集合交集操作.name,以找到,说,Person('Bar', -1).intersection(union_list) #= [Person("Bar", -1), Person("Bar", 22), Person("Bar", 24)]。(此时的典型.intersection()不会给我任何东西,我不能覆盖__hash____eq__Person课堂上,因为这会覆盖原来的集合并集(我认为

在 Python 2.x 中执行此操作的最佳方法是什么?

编辑:请注意,解决方案不必依赖set. 但是,我需要找到联合,然后是交叉点,所以感觉这适合一组(但我愿意接受使用你认为值得的任何魔法的解决方案,只要它解决了我的问题!)

4

6 回答 6

7

听上去像

>>> class Person:
...     def __init__(self, name, age):
...         self.name = name
...         self.age = age
...     def __eq__(self, other):
...         return self.name == other.name
...     def __hash__(self):
...         return hash(self.name)
...     def __str__(self):
...         return self.name
...
>>> l1 = [Person("Foo", 21), Person("Bar", 22)]
>>> l2 = [Person("Foo", 21), Person("Bar", 24)]
>>> union_list = list(set(l1).union(l2))
>>> [str(l) for l in union_list]
['Foo', 'Bar']

是你想要的,因为name是你唯一的钥匙?

于 2012-05-30T09:18:20.803 回答
2

我讨厌回答自己的问题,所以我暂时不会将其标记为“答案”。

原来这样做的方法如下:

import types
p = Person("Bar", -1)
new_hash_method = lambda obj: hash(obj.name)
p.__hash__ = types.MethodType(new_hash_method, p)
for i in xrange(0, len(union_list)):
    union_list[i].__hash__ = types.MethodType(new_hash_method, union_list[i])
set(union_list).intersection(p)

它肯定很脏并且依赖于types.MethodType,但它没有迄今为止提出的最佳解决方案(glglgl 的解决方案)那么密集,因为我的实际union_list可能包含数千个项目,所以这将节省我每次运行时重新创建对象这个相交程序。

于 2012-05-31T06:58:53.990 回答
2

怎么样:

d1 = {p.name:p for p in l1}
d2 = {p.name:p for p in l2}

intersectnames = set(d1.keys()).intersection(d2.keys)
intersect = [d1[k] for k in intersectnames]

扔给intersectnames你的 ORM 可能会更快,在这种情况下你不会构建字典,只需在列表中收集名称。

于 2012-05-31T07:15:13.250 回答
1

这很笨拙,但是...

set(p for p in union_list for q in l2 if p.name == q.name and p.age != q.age) | (set(p for p in l2 for q in union_list if p.name == q.name and p.age != q.age))
# {person(name='Bar', age=22), person(name='Bar', age=24)}
于 2012-05-30T09:09:55.937 回答
1

如果您希望age与比较无关,您应该覆盖__hash__()and __eq__()inPerson尽管您的Object.

如果您仅在此(和类似)上下文中需要此行为,您可以创建一个包装器对象,其中包含Person并且行为不同,例如

class PersonWrapper(Object):
    def __init__(self, person):
        self.person = person
    def __eq__(self, other):
        if hasattr(other, 'person'):
            return self.person.name == other.person.name
        else:
            return self.person.name == other.name
    def __hash__(self):
        return hash(self.person.name)

然后做

union_list = list(set(PersonWrapper(i) for i in l1).union(PersonWrapper(i) for i in l2))
# [Person("Foo", 21), Person("Bar", 22), Person("Bar", 24)]

(未经测试)

于 2012-05-30T09:50:29.653 回答
0

__hash__如果你想使用这样的集合,你必须重写和比较方法。

如果你不这样做,那么

Person("Foo", 21) == Person("Foo", 21)

永远是假的。

如果您的对象由 ORM 管理,那么您必须检查它如何比较对象。通常它只查看对象 id,并且只有在两个对象都被管理的情况下,比较才有效。如果您尝试将您从 ORM 获得的对象与您在保存到数据库之前自己创建的实例进行比较,那么它们可能会有所不同。无论如何,ORM 不应该有您提供自己的比较逻辑的问题。

但是如果由于某些原因你不能覆盖__hash__and __eq__,那么你不能使用集合来与原始对象进行交集和联合。你可以:

  • 自己计算交集/联合
  • 创建一个可比较的包装类:

    class Person:                    
        def __init__(self, name, age):                                                      
            self.name = name                                                                
            self.age = age                                                                  
    
    l1 = [Person("Foo", 21), Person("Bar", 22)]                                             
    l2 = [Person("Foo", 21), Person("Bar", 24)]                                             
    
    class ComparablePerson:
        def __init__(self, person):
            self.person = person
    
        def __hash__(self):
            return hash(self.person.name) + 31*hash(self.person.age)
    
        def __eq__(self, other):
            return (self.person.name == other.person.name and
                    self.person.age == other.person.age)
        def __repr__(self):
            return "<%s - %d>" % (self.person.name, self.person.age)
    
    c1 = set(ComparablePerson(p) for p in l1)
    c2 = set(ComparablePerson(p) for p in l2)
    
    print c1
    print c2
    print c1.union(c2)
    print c2.intersection(c1)
    
于 2012-05-30T08:59:04.563 回答