Python自定义集合交集

时间:2012-05-30 08:48:04

标签: python python-2.x set-intersection

因此,存在一种通过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。但是,我需要找到工会然后交叉点,所以感觉这对于一套是合适的(但是我愿意接受使用你认为值得的任何魔法的解决方案,只要它能解决我的问题!)

6 个答案:

答案 0 :(得分:6)

听起来像

>>> 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是您唯一的密钥?

答案 1 :(得分:1)

如果您希望age与比较无关,则应覆盖__hash__()中的__eq__()Person,尽管您已将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)]

然后再做

{{1}}

(未测试的)

答案 2 :(得分:1)

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

原来这样做的方法如下:

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可能包含数千个项目的顺序,因此,每次运行此交叉过程时,这都将使我无需重新创建对象。

答案 3 :(得分:1)

怎么样:

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可能会更快,在这种情况下您不会构建词典,只需在列表中收集名称。

答案 4 :(得分:0)

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

如果你不这样做,那么

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

总是假的。

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

但是如果由于某些原因你无法覆盖__hash____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)
    

答案 5 :(得分:0)

这很笨重但是......

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)}