Pytoniczny sposób ignorowania ostatniego elementu podczas ustawiania różnicy

11

Powiedzmy, że mam dwa 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')}

Teraz chcę znaleźć ustaloną różnicę, b \ aale ignorując ostatni element z każdej krotki. To tak, jakby zrobić coś takiego:

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

Oczekiwany wynik:

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

Czy istnieje jakiś oczywisty / pytonowy sposób na osiągnięcie tego bez konieczności ręcznego iterowania każdego zestawu i sprawdzania każdego z nich tuple[:3]?

Grajdeanu Alex.
źródło
3
Moją początkową myślą jest uczynienie ich klasami, zdefiniowanie operatora porównania
Kenny Ostrom,
2
podklasę seti zastąpić operację różnicową. Nie ma gotowego rozwiązania, o którym wiem i wątpię, by istniało.
Ev. Kounis,
Nie ma „key = ...” ani czegoś podobnego (jak w przypadku sortowania (..)) dla zestawów. Krotki są niezmienne i mieszalne i są porównywane na podstawie ich skrótu. Usunięcie jednego elementu unieważniłoby skrót. Więc nie - niemożliwe. Jeśli nie potrzebujesz wartości, możesz utworzyć zestawy 3-częściowe:aa = { t[:3] for t in a }
Patrick Artner,
2
@ AK47 Różnica (zbiór) między dwoma zestawami S i T jest zapisywana S ∖ T i oznacza zbiór, który składa się z elementów S, które nie są elementami T: x∈S ∖ T⟺x∈S∧x∉T
Grajdeanu Alex.
Podklasuj tuplei zastępuj operator różnicy
Pynchia,

Odpowiedzi:

10

Oto, w jaki sposób możesz napisać własną klasę, aby zastąpić normalne zachowanie mieszania krotki:

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)

z wyjściem

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

Aby zmodyfikować sposób działania zestawów krotek, musimy zmodyfikować sposób mieszania krotek.

Od tutaj ,

Obiekt jest haszowalny, jeśli ma wartość skrótu, która nigdy się nie zmienia w trakcie jego życia (potrzebuje __hash__()metody), i można go porównać do innych obiektów (potrzebuje __eq__()metody). Obiekty haszujące, które porównują równe, muszą mieć tę samą wartość skrótu.

Hashability sprawia, że ​​obiekt nadaje się do użycia jako klucz słownika i element członkowski, ponieważ te struktury danych wewnętrznie używają wartości skrótu.

Tak więc w celu uczynienia mieszania zignorować ostatni element, musimy przeciążać metody Dunder __eq__i __hash__odpowiednio. Nie jest to wcale takie trudne, ponieważ wszystko, co musimy zrobić, to odciąć ostatni element, a następnie przekazać odpowiednie metody normalne tuple.

Dalsza lektura:

Izaak van Dongen
źródło
1
Bardzo schludny! Czy mógłbyś również opisać, jak to działa? Może być warto dla tych, którzy przeczytają to rozwiązanie.
Grajdeanu Alex.
@GrajdeanuAlex. Dodałem krótkie wyjaśnienie :). Naprawdę polega to na połączeniu przeciążeń operatora i działania funkcji mieszania w Pythonie.
Izaak van Dongen
2

Oto jeden sposób definiowania ai bwykazy zamiast zestawów, ponieważ wydaje mi się, że najbardziej proste rozwiązanie do przodu zakłada indeksowanie 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
źródło
1
To, jeśli się nie mylę, to O (n), ponieważ używam zestawu do wyszukiwania. Chociaż myślę, że odpowiedź Izaaka van Dongena jest znacznie bardziej elegancka @konrad
yatu
1
Masz całkowitą rację, użycie (i wyliczenie) listy wytrąciło mnie z równowagi, ale oczywiście pewna różnica zestawu również musi się powtarzać w stosunku do pierwszego zestawu.
Konrad Rudolph,
1

Zestawy działają dobrze. Twoje dane nie działają poprawnie. Jeśli wyglądają inaczej, ale w rzeczywistości są takie same, zdefiniuj typ danych, który zachowuje się tak, jak chcesz. Następnie zestaw działa sam świetnie.

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
źródło
3
Zdefiniowałeś __repr__i __hash__pod względem krotek, ale nie __eq__. Czy nie byłoby też krótsze użycie krotek tutaj? W rzeczywistości możesz użyć krojenia tutaj i w __hash__celu dalszego skrócenia kodu.
Konrad Rudolph,
Tak, właśnie krotka subklasy była znaczną poprawą zadanego pytania.
Kenny Ostrom,