Modo pitonico di ignorare l'ultimo elemento quando si fa la differenza impostata


11

Diciamo che ho due 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')}

Ora, quello che voglio fare è trovare la differenza impostata b \ ama ignorando l'ultimo elemento di ogni tupla. Quindi è proprio come fare qualcosa del genere:

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

Uscita prevista:

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

Esiste un modo ovvio / pitonico per raggiungere questo obiettivo senza dover iterare manualmente su ogni set e verificare con ciascuno di essi tuple[:3]?


3
Il mio pensiero iniziale è quello di renderli classi, definire l'operatore di confronto
Kenny Ostrom il

2
sottoclasse sete sovrascrivere l'operazione di differenza. Non esiste una soluzione pronta all'uso che conosco e dubito che esista.
Ev. Kounis,

Non esiste "key = ..." o qualcosa di simile (come per sort (..)) per i set. Le tuple sono immutabili e lavabili e vengono confrontate in base al loro hash. La rimozione di un elemento annullerebbe l'hash. Quindi No - impossibile. Se non è necessario il valore, è possibile creare set in 3 parti:aa = { t[:3] for t in a }
Patrick Artner, il

2
@ AK47 La differenza (insieme) tra due insiemi S e T è scritta S ∖ T e indica l'insieme costituito dagli elementi di S che non sono elementi di T: x∈S ∖ T⟺x∈S∧x∉T
Grajdeanu Alex.

Sottoclasse tuplee sovrascrive l'operatore differenza
Pynchia

Risposte:


10

Ecco come potresti scrivere la tua classe per sovrascrivere il normale comportamento di hash di una tupla:

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)

con uscita

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

Per modificare il modo in cui si comportano le serie di tuple, dobbiamo modificare il modo in cui le tuple vengono hash.

Da qui ,

Un oggetto è hash se ha un valore hash che non cambia mai durante la sua vita (ha bisogno di un __hash__()metodo) e può essere paragonato ad altri oggetti (ha bisogno di un __eq__()metodo). Gli oggetti hash che si equivalgono devono avere lo stesso valore hash.

La hashability rende un oggetto utilizzabile come chiave di dizionario e membro set, poiché queste strutture di dati utilizzano internamente il valore di hash.

Quindi, per fare in modo che l'hash ignori l'ultimo elemento, dobbiamo sovraccaricare i metodi del dunder __eq__e in modo __hash__appropriato. Questo non finisce per essere così difficile perché tutto ciò che dobbiamo fare è tagliare l'ultimo elemento e quindi delegare ai metodi appropriati di un normale tuple.

Ulteriori letture:


1
Molto pulito! Potresti anche descrivere un po 'come funziona? Potrebbe valere la pena per coloro che leggeranno questa soluzione.
Grajdeanu Alex.

@GrajdeanuAlex. Ho aggiunto una breve spiegazione :). In realtà sta solo combinando pezzi di sovraccarico dell'operatore e come funziona l'hashing in Python.
Izaak van Dongen,

2

Ecco un approccio che definisce ae bcon liste piuttosto che insiemi, poiché mi sembra che la soluzione più semplice implichi l'indicizzazione 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')]

1
Questo se non sbaglio è O (n), poiché utilizzo un set per la ricerca. Anche se penso che la risposta di Izaak van Dongen sia molto più elegante @konrad
yatu

1
Hai perfettamente ragione, l'uso di (e l'enumerazione sopra) di un elenco mi ha scartato, ma ovviamente una serie di differenze deve anche scorrere sul primo set.
Konrad Rudolph,

1

Imposta funziona bene. Sono i tuoi dati che non funzionano correttamente. Se sembrano diversi ma in realtà sono uguali, allora definisci un tipo di dati che si comporti come desideri. Quindi il set funziona alla grande da solo.

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


3
Hai definito __repr__e __hash__in termini di tuple, ma non __eq__. Non sarebbe più breve usare anche le tuple qui? In effetti, puoi usare lo slicing qui e in __hash__per abbreviare ulteriormente il codice.
Konrad Rudolph,

Sì, solo la tupla della sottoclasse è stata un grande miglioramento della domanda.
Kenny Ostrom,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.