2012-05-30 28 views
5

所以,存在一種簡單的方法來計算經由set.intersection兩個集合的交集()。不過,我有以下問題:Python的自定義設置相交

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()不會給我什麼,我不能在Person類中重寫__hash____eq__,因爲這會覆蓋原有的並集(我認爲

什麼是應該做的最好辦法這在Python 2.x的

編輯:請注意,解決方案不依靠set不過,我需要找到工會,然後交叉,所以感覺這樣服從於一個集合。 (但我願意接受使用任何你認爲有價值的魔法的解決方案,只要它解決了我的問題!)

+0

我不明白你想要的結果。您能否請*解釋*結果應該包含什麼? –

+0

錯誤廢話,那應該是.union,而不是.intersection。我已經更新了這個問題 - 讓我知道這是否更清楚? –

+0

由於示例代碼沒有您聲明的結果,我仍然有點困惑。 –

回答

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可能包含數千個項目的順序,因此每次運行此交集過程時都可以節省重新創建對象的時間。

+0

這實際上工作嗎?該文檔指出類似'__hash__'的魔術方法在類中查找,而不是實例。 https://docs.python.org/3/reference/datamodel.html#special-lookup –

+0

實際上,它看起來像舊式樣類,但不適用於新式樣類:https://docs.python.org /2/reference/datamodel.html#special-method-lookup-for-old-style-classes –

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) 
    
+1

查看我的評論(關於原始問題);覆蓋已經由ORM處理了。我會更新這個問題來反映這一點。 –

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

聽起來像

>>> 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是你的唯一關鍵?

+0

啊,不,我正在使用的ORM已經提供了__eq__和__hash__方法(因此,set.union()已經產生了'理性'結果)。我正在尋找一種方法來執行交叉操作,它只*使用類的成員之一作爲鍵,因此不能覆蓋'__hash__'或'__eq__'。 –

+0

我明白了,那麼glglgl的解決方案可能適合? –

1

如果你想在age是不相關的相對於比較,你應該重寫__hash__()__eq__()Person雖然你有它在你的Object

如果需要此行爲僅在這個(和similiar)環境中,你可以創建一個包裝對象,它包含了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)] 

(未經測試)

+0

問題是我需要'__hash__'和'__eq__'方法,否則''.union()'不會像它那樣工作。 –

+0

嗯,有趣。所以沒有重建對象就沒有辦法做到這一點?我知道C++讓我選擇通過自定義比較器; Python不具有相同的能力? –

+0

有一種方法可以用像'sorted()'這樣的函數來實現,在這裏你可以給cmp函數以及''key'函數,但是不能用'set's,唉... – glglgl

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,在這種情況下,你不會建立詞典,只是收集列表的名稱。