Піфонічний спосіб ігнорування останнього елемента при виконанні заданої різниці


11

Скажімо, у мене є два 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')}

Тепер, що я хочу зробити, це знайти різницю набору, b \ aале ігноруючи останній елемент з кожного кортежу. Тож це просто так:

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

Очікуваний вихід:

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

Чи є якийсь очевидний / пітонічний спосіб цього досягти без необхідності вручну перебирати над кожним набором і не перевіряти проти кожного tuple[:3]?


3
Моя початкова думка - зробити їх класами, визначити оператора порівняння
Кенні Остром

2
підклас setта перезаписати різницю. Я не знаю жодного нестандартного рішення, і я сумніваюся, що таке існує.
Єв. Куніс

Для множин немає "key = ..." або чогось подібного (як для сортування (..)). Кортежі є незмінні та хешируемими і порівнюються на основі їх хешу. Видалення одного елемента втратить хеш. Так Ні - неможливо. Якщо вам не потрібне значення, ви можете створити 3-х aa = { t[:3] for t in a }
частинні

2
@ AK47 Різниця (множина) між двома множинами S і T пишеться S ∖ T, і означає множину, що складається з елементів S, які не є елементами T: x∈S ∖ T⟺x∈S∧x∉T
Грайдеану Алекс.

Підклас tupleі переопределить оператор різниці
Pynchia

Відповіді:


10

Ось як ви можете написати власний клас, щоб змінити нормальну хеширувальну поведінку кортежу:

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)

з виходом

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

Щоб змінити спосіб поведінки наборів кортежів, нам доведеться модифікувати хеширование кортежів.

З тут ,

Об'єкт є хешируемым, якщо він має хеш-значення, яке ніколи не змінюється протягом свого життя (йому потрібен __hash__()метод), і його можна порівняти з іншими об'єктами (йому потрібен __eq__()метод). Об'ємні об'єкти, які порівнюють рівні, повинні мати однакове хеш-значення.

Можливість використання об'єкта використовується як ключ словника та набір елементів, оскільки ці структури даних використовують хеш-значення всередині.

Тож для того, щоб хеш ігнорував останній елемент, нам доведеться перевантажувати методи обходу __eq__та __hash__відповідним чином. Це в кінцевому підсумку не так складно, тому що все, що нам потрібно зробити, це відрізати останній елемент і потім делегувати відповідним методам звичайного tuple.

Подальше читання:


1
Дуже акуратно! Чи можете ви також трохи описати, як це працює? Це може бути варте тих, хто прочитає це рішення.
Грайдеану Алекс.

@GrajdeanuAlex. Я додав коротке пояснення :). Дійсно, це просто поєднання бітів і частин перевантаження оператора і як хешування працює в Python.
Ізаак ван Донген

2

Ось один підхід, який визначає aі bзі списками, а не з наборами, оскільки мені здається, що найбільш пряме рішення передбачає індексацію 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
Це, якщо я не помиляюся, є O (n), оскільки я використовую набір для пошуку. Хоча я думаю, що відповідь Ізаака
ван Донгена

1
Ви абсолютно праві, використання списку (і перерахування над ним) відкинуло мене, але, звичайно, різниця у наборі також повинна повторювати перший набір.
Конрад Рудольф

1

Набори добре працюють. Це ваші дані, які не працюють правильно. Якщо вони виглядають по-різному, але насправді однакові, то визначте тип даних, який веде себе так, як вам потрібно. Тоді набір чудово працює самостійно.

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
Ви визначили __repr__і __hash__з точки зору кортежів, але ні __eq__. Чи не буде коротше і тут використовувати кортежі? Насправді, ви можете використовувати нарізки тут і __hash__для скорочення коду.
Конрад Рудольф

Так, просто підкласинг кортежу було великим поліпшенням питання, що його задавали.
Кенні Остром
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.