2012-05-30 7 views
5

So gibt es eine einfache Möglichkeit, den Schnittpunkt von zwei Mengen über set.intersection() zu berechnen. Aber ich habe folgendes Problem:.Python benutzerdefinierte Schnittmenge

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 ist eine Basis-Klasse durch meine ORM zur Verfügung gestellt, die grundlegende __hash__ und __eq__ Funktionalität implementiert, die im wesentlichen jedes Mitglied der Klasse der Hash fügt Mit anderen Worten, die __hash__ wird ein Hash von jedem Element der Klasse zurückgegeben)

In diesem Stadium würde Ich mag einen Satz Schnittoperation durch .name laufen nur, zu finden, sagen wir, Person('Bar', -1).intersection(union_list) #= [Person("Bar", -1), Person("Bar", 22), Person("Bar", 24)]. (Die typischen .intersection() an dieser Stelle würde mir nicht alles geben, ich nicht __hash__ oder __eq__ auf der Person Klasse außer Kraft setzen kann, da dies die ursprünglichen Vereinigungs außer Kraft setzen würde (I denken)

was der besten Weg ist zu tun in Python 2.x dies

EDIT: Beachten sie, dass die Lösung nicht auf einem set zu verlassen hat ich brauche aber die Gewerkschaften zu finden und dann Kreuzungen, so es so fühlt, ist offen für einen Satz. (Aber ich bin bereit, Lösungen zu akzeptieren, die jede Magie benutzen, die Sie für angemessen halten, solange sie mein Problem löst!)

+0

Ich verstehe Ihr gewünschtes Ergebnis nicht. Könnten Sie bitte * erklären * was das Ergebnis enthalten soll? –

+0

Err mist, das sollte .union sein, nicht .intersection. Ich habe die Frage aktualisiert - lassen Sie mich wissen, ob dies klarer ist? –

+0

Ich bin immer noch ein wenig verwirrt, da der Beispielcode nicht das gewünschte Ergebnis hat. –

Antwort

1

Ich hasse es, meine eigenen Fragen zu beantworten, also werde ich es noch eine Weile als "Antwort" hinstellen.

Schaltet den Weg, dies zu tun ist wie folgt:

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) 

Es ist sicherlich schmutzig und es stützt sich auf types.MethodType, aber es ist weniger intensiv als die beste Lösung bisher (glglgl-Lösung) als meine eigentlichen vorgeschlagen union_list kann potenziell in der Größenordnung von Tausenden von Elementen enthalten sein, so dass ich dadurch jedes Mal, wenn ich diese Schnittprozedur ausführe, Objekte neu erstellen kann.

+0

Funktioniert das tatsächlich? Die Dokumentation zeigt an, dass magische Methoden wie "__hash__" in der Klasse nachgeschlagen werden, nicht in der Instanz. , Sieht aus wie es funktioniert für alte Stil-Klassen, aber nicht für neue Stilklassen https://docs.python.org/3/reference/datamodel.html#special-lookup –

+0

Eigentlich: https://docs.python.org /2/reference/datamodel.html#special-method-lookup-for-old-style-classes –

0

Sie müssen __hash__ und die Vergleichsmethoden überschreiben, wenn Sie solche Sätze verwenden möchten.

Wenn Sie dies nicht tun, dann

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

wird immer falsch sein.

Wenn Ihre Objekte von einem ORM verwaltet werden, müssen Sie überprüfen, wie Objekte verglichen werden. Normalerweise sieht es nur die Objekte ID und Vergleich funktioniert nur, wenn beide Objekte verwaltet werden. Wenn Sie versuchen, ein Objekt, das Sie vom ORM erhalten haben, mit einer Instanz zu vergleichen, die Sie selbst erstellt haben, bevor es in der db gespeichert wurde, dann sind sie wahrscheinlich anders. Wie auch immer, ein ORM sollte keine Probleme damit haben, eine eigene Vergleichslogik zu liefern.

Wenn Sie jedoch aus bestimmten Gründen __hash__ und __eq__ nicht überschreiben können, können Sie keine Sätze für Schnittmengen und Vereinigungen mit den Originalobjekten verwenden. Sie könnten:

  • die Kreuzung/union berechnen sich
  • eine Wrapper-Klasse erstellen, die vergleichbar ist:

    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

Siehe meinen Kommentar (zur ursprünglichen Frage); Der Override wird bereits von einem ORM behandelt. Ich werde die Frage aktualisieren, um dies zu berücksichtigen. –

0

Dies ist klobig, aber ...

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

Klingt wie

>>> 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'] 

ist was Sie wollen, da name ist Ihr einzigartiger Schlüssel?

+0

Ah, nein, das ORM, das ich verwende, stellt bereits eine __eq__ und __hash__ Methode zur Verfügung (und als solches erzeugt set.union() bereits 'sane' Ergebnisse). Ich suche nach einer Möglichkeit, eine Kreuzungsoperation auszuführen, die * nur * eines der Mitglieder der Klasse als Schlüssel verwendet und somit '__hash__' oder' __eq__' nicht überschreiben kann. –

+0

ich sehe, dann wäre vielleicht glglgl's lösung geeignet? –

1

Wenn Sie die age als irrelevant in Bezug auf den Vergleich wollen, sollten Sie __hash__() und __eq__() in Person außer Kraft setzen, obwohl Sie es in Ihrem Object haben.

Wenn Sie dieses Verhalten müssen nur in diesem (und ähnliche) Kontexte, könnten Sie ein Wrapper-Objekt erstellen, das die Person enthält und verhält sich anders, wie

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) 

und dann tun

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

(ungetestet)

+0

Das Problem ist, dass ich die Methoden "__hash__" und "__eq__" so brauche, wie sie sind, sonst funktioniert '.union()' nicht so, wie es ist. –

+0

Hmm, interessant. Also gibt es keine Möglichkeit, dies zu tun, ohne Objekte zu rekonstruieren? Ich weiß, C++ gibt mir die Möglichkeit, einen benutzerdefinierten Komparator zu übergeben; Python hat nicht die gleiche Fähigkeit? –

+0

Es gibt einen Weg, um mit Funktionen zu tun, wie 'sortiert()', wo man geben kann eine 'cmp' Funktion sowie eine' key' Funktion, aber nicht mit 'Set's, ach ... – glglgl

1

Wie wäre:

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] 

Es könnte schneller sein intersectnames in Ihrem ORM zu werfen, in welchem ​​Fall würden Sie Wörterbücher nicht bauen, nur Namen in Listen sammeln.

Verwandte Themen