Cara Pythonic mengabaikan elemen terakhir saat melakukan set perbedaan

11

Katakanlah saya punya dua 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')}

Sekarang, yang ingin saya lakukan adalah menemukan perbedaan set b \ atetapi mengabaikan elemen terakhir dari setiap tuple. Jadi seperti melakukan sesuatu seperti ini:

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

Output yang diharapkan:

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

Apakah ada cara yang jelas / pythonic untuk mencapai ini tanpa harus secara manual mengulangi setiap set dan memeriksa masing-masing tuple[:3]?

Grajdeanu Alex.
sumber
3
Pikiran awal saya adalah membuat mereka kelas, mendefinisikan operator perbandingan
Kenny Ostrom
2
subkelas setdan menimpa operasi perbedaan. Tidak ada solusi out-of-the-box yang saya tahu dan saya ragu ada.
Ev. Kounis
Tidak ada "key = ..." atau yang serupa (seperti untuk sort (..)) untuk set. Tuples tidak dapat diubah dan hashable dan dibandingkan berdasarkan hash mereka. Menghapus satu elemen akan membatalkan hash. Jadi Tidak - tidak mungkin. Jika Anda tidak membutuhkan nilainya, Anda dapat membuat set 3-bagian:aa = { t[:3] for t in a }
Patrick Artner
2
@ AK47 Perbedaan (set) antara dua set S dan T ditulis S ∖ T, dan berarti set yang terdiri dari elemen S yang bukan elemen T: x∈S ∖ T⟺x∈S∧x∉T
Grajdeanu Alex.
Subkelas tupledan timpa operator perbedaan
Pynchia

Jawaban:

10

Inilah cara Anda dapat menulis kelas Anda sendiri untuk mengesampingkan perilaku hashing normal tuple:

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)

dengan output

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

Untuk mengubah cara tuple berperilaku, kita harus memodifikasi cara tuple di-hash.

Dari sini ,

Objek hashable jika memiliki nilai hash yang tidak pernah berubah selama masa hidupnya (ia membutuhkan __hash__()metode), dan dapat dibandingkan dengan objek lain (itu membutuhkan __eq__()metode). Objek yang dapat di-hasashkan yang membandingkannya harus memiliki nilai hash yang sama.

Hashability membuat objek dapat digunakan sebagai kunci kamus dan anggota yang ditetapkan, karena struktur data ini menggunakan nilai hash secara internal.

Jadi untuk membuat hashing mengabaikan elemen terakhir, kita harus membebani metode dunder __eq__dan secara __hash__tepat. Ini tidak berakhir menjadi sangat sulit karena yang harus kita lakukan adalah memotong elemen terakhir dan kemudian mendelegasikannya ke metode normal yang sesuai tuple.

Bacaan lebih lanjut:

Izaak van Dongen
sumber
1
Sangat rapi! Bisakah Anda juga menjelaskan sedikit cara kerjanya? Mungkin bermanfaat bagi mereka yang akan membaca solusi ini.
Grajdeanu Alex.
@GrajdeanuAlex. Saya telah menambahkan penjelasan singkat :). Sungguh itu hanya menggabungkan potongan-potongan operator kelebihan dan bagaimana hashing bekerja di Python.
Izaak van Dongen
2

Inilah satu pendekatan yang mendefinisikan adan bdengan daftar daripada set, karena menurut saya solusi yang paling mudah adalah pengindeksan 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
sumber
1
Ini jika saya tidak salah adalah O (n), karena saya menggunakan set untuk pencarian. Meskipun saya pikir jawaban Izaak van Dongen jauh lebih elegan @konrad
yatu
1
Anda sepenuhnya benar, penggunaan (dan pencacahan atas) daftar membuat saya kecewa, tetapi tentu saja perbedaan himpunan juga perlu diulangi pada set pertama.
Konrad Rudolph
1

Set berfungsi dengan baik. Data Anda yang tidak berfungsi dengan benar. Jika mereka terlihat berbeda tetapi sebenarnya sama, maka tentukan tipe data yang berperilaku seperti yang Anda inginkan. Kemudian mengatur bekerja dengan baik pada dirinya sendiri.

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
sumber
3
Anda mendefinisikan __repr__dan __hash__dalam hal tupel, tetapi tidak __eq__. Bukankah lebih pendek untuk menggunakan tuple di sini juga? Bahkan, Anda bisa menggunakan slicing di sini dan di __hash__untuk mempersingkat kode lebih lanjut.
Konrad Rudolph
Aye, hanya subklas tuple adalah peningkatan besar untuk pertanyaan yang diajukan.
Kenny Ostrom