Pythonische Methode zum Ignorieren des letzten Elements bei der Einstellung der Differenz

11

Nehmen wir an, ich habe zwei set()s:

a = {('1', '2', '3', 'a'), ('1', '2', '4', 'a'), ('1', '2', '5', 'b')}
b = {('1', '2', '3', 'b'), ('1', '2', '4', 'b'), ('1', '2', '6', 'b')}

Was ich jetzt tun möchte, ist, den eingestellten Unterschied zu finden, b \ aaber das letzte Element aus jedem Tupel zu ignorieren. Es ist also so, als würde man so etwas machen:

a = {('1', '2', '3'), ('1', '2', '4'), ('1', '2', '5')}
b = {('1', '2', '3'), ('1', '2', '4'), ('1', '2', '6')}

In[1]: b - a
Out[1]: {('1', '2', '6')}

Erwartete Ausgabe:

b \ a = {('1', '2', '6', 'b')}

Gibt es eine offensichtliche / pythonische Möglichkeit, dies zu erreichen, ohne jeden Satz manuell durchlaufen und mit jedem vergleichen zu müssen tuple[:3]?

Grajdeanu Alex.
quelle
3
Mein erster Gedanke ist, sie zu Klassen zu machen, Vergleichsoperator zu definieren
Kenny Ostrom
2
Unterklasse setund überschreiben Sie die Differenzoperation. Es gibt keine sofort einsatzbereite Lösung, die mir bekannt ist, und ich bezweifle, dass es eine gibt.
Ev. Kounis
Es gibt kein "key = ..." oder ähnliches (wie für sort (..)) für Sets. Tupel sind unveränderlich und hashbar und werden anhand ihres Hashs verglichen. Das Entfernen eines Elements würde den Hash ungültig machen. Also nein - nicht möglich. Wenn Sie den Wert nicht benötigen, können Sie 3-teilige Sets erstellen:aa = { t[:3] for t in a }
Patrick Artner
2
@ AK47 Die (Mengen-) Differenz zwischen zwei Mengen S und T wird als S ∖ T geschrieben und bedeutet die Menge, die aus den Elementen von S besteht, die keine Elemente von T sind: x∈S ∖ T⟺x∈S∧x∉T
Grajdeanu Alex.
Unterklasse tupleund überschreiben Sie den Differenzoperator
Pynchia

Antworten:

10

So können Sie Ihre eigene Klasse schreiben, um das normale Hashing-Verhalten eines Tupels zu überschreiben:

a_data = [('1', '2', '3', 'a'), ('1', '2', '4', 'a'), ('1', '2', '5', 'b')]
b_data = [('1', '2', '3', 'b'), ('1', '2', '4', 'b'), ('1', '2', '6', 'b')]

class HashableIgnoresLastElement(tuple):
    def __eq__(self, other):
        return self[:-1] == other[:-1]

    def __hash__(self):
        return hash(self[:-1])

a = set(map(HashableIgnoresLastElement, a_data))
b = set(map(HashableIgnoresLastElement, b_data))

print(b - a)

mit Ausgabe

{('1', '2', '6', 'b')}

Um das Verhalten von Tupelgruppen zu ändern, müssen wir das Hashing von Tupeln ändern.

Von hier ,

Ein Objekt ist hashbar, wenn es einen Hashwert hat, der sich während seiner Lebensdauer nie ändert (es benötigt eine __hash__()Methode) und mit anderen Objekten verglichen werden kann (es benötigt eine __eq__()Methode). Hashbare Objekte, die gleich sind, müssen denselben Hashwert haben.

Durch die Hashability kann ein Objekt als Wörterbuchschlüssel und als festgelegtes Element verwendet werden, da diese Datenstrukturen den Hashwert intern verwenden.

Also, um das Hashing ignoriert das letzte Element zu machen, müssen wir die dunder Methoden überlasten __eq__und __hash__angemessen. Dies ist nicht so schwierig, da wir nur das letzte Element abschneiden und dann an die entsprechenden Methoden eines Normalen delegieren müssen tuple.

Weiterführende Literatur:

Izaak van Dongen
quelle
1
Sehr gepflegt! Könnten Sie auch ein bisschen beschreiben, wie das funktioniert? Es könnte sich für diejenigen lohnen, die diese Lösung durchlesen.
Grajdeanu Alex.
@ GrajdeanuAlex. Ich habe eine kurze Erklärung hinzugefügt :). In Wirklichkeit geht es nur darum, Teile der Operatorüberladung zu kombinieren und wie Hashing in Python funktioniert.
Izaak van Dongen
2

Hier ist ein Ansatz, der Listen anstelle von Mengen definiert aund bverwendet, da mir die einfachste Lösung die Indizierung impliziert b:

a = [('1', '2', '3', 'a'), ('1', '2', '4', 'a'), ('1', '2', '5', 'b')]
b = [('1', '2', '3', 'b'), ('1', '2', '4', 'b'), ('1', '2', '6', 'b')]

# reconstruct the sets of tuples removing the last elements
a_ = {tuple(t) for *t, _ in a}
b_ = [tuple(t) for *t, _ in b]

# index b based on whether an element in a_
[b[ix] for ix, j in enumerate(b_) if j not in a_]
# [('1', '2', '6', 'b')]
Yatu
quelle
1
Wenn ich mich nicht irre, ist dies O (n), da ich einen Satz für die Suche verwende. Obwohl ich denke, dass Izaak van Dongens Antwort viel eleganter ist @konrad
yatu
1
Sie haben völlig Recht, die Verwendung (und Aufzählung) einer Liste hat mich umgehauen, aber natürlich muss ein Satzunterschied auch über den ersten Satz iteriert werden.
Konrad Rudolph
1

Sets funktionieren gut. Es sind Ihre Daten, die nicht richtig funktionieren. Wenn sie unterschiedlich aussehen, aber tatsächlich gleich sind, definieren Sie einen Datentyp, der sich wie gewünscht verhält. Dann funktioniert das Set von alleine großartig.

class thing:
    def __init__(self, a, b, c, d):
        self.a, self.b, self.c, self.d = a, b, c, d

    def __repr__(self):
        return (str((self.a, self.b, self.c, self.d)))

    def __hash__(self):
        return hash((self.a, self.b, self.c))

    def __eq__(self, other):
        return self.a == other.a and self.b == other.b and self.c == other.c       

a = {thing('1', '2', '3', 'a'), thing('1', '2', '4', 'a'), thing('1', '2', '5', 'b')}
b = {thing('1', '2', '3', 'b'), thing('1', '2', '4', 'b'), thing('1', '2', '6', 'b')}
print (b - a)

{('1', '2', '6', 'b')}

Kenny Ostrom
quelle
3
Sie definiert __repr__und __hash__in Form von Tupeln, aber nicht __eq__. Wäre es nicht kürzer, auch hier Tupel zu verwenden? Tatsächlich können Sie hier und in Slicing verwenden __hash__, um den Code weiter zu verkürzen.
Konrad Rudolph
Ja, nur das Unterklassen von Tupeln war eine große Verbesserung der gestellten Frage.
Kenny Ostrom