Чи є більш елегантний спосіб виразити ((x == a і y == b) або (x == b і y == a))?


108

Я намагаюся оцінити ((x == a and y == b) or (x == b and y == a))в Python, але це здається дещо багатослівним. Чи є більш елегантний спосіб?


8
Залежить від типу об’єктів x,y, a,b: це ints / floats / string, довільні об'єкти чи що? Якби вони були побудованів типах і можна було тримати x,yі a,bв упорядкованому порядку, тоді можна було б уникнути другої гілки. Зауважте, що створення набору спричинить x,y, a,bхешування кожного з чотирьох елементів , який може бути, а може і не тривіальним, або матиме значення для продуктивності, що залежить повністю від типу об'єктів.
smci

18
Майте на увазі людей, з якими ви працюєте, і тип проекту, який ви розробляєте. Я використовую трохи Python тут і там. Якби хтось зашифрував одну з відповідей тут, я б повністю мусив гугл, що відбувається. Ваш умовний варіант читається майже незалежно від того.
Areks

3
Я б не переймався жодною із замін. Вони більш стислі, але не такі чіткі (ІМХО), і я думаю, що все буде повільніше.
Barmar

22
Це класична проблема XYAB.
Ташус

5
І взагалі, якщо ви маєте справу з колекціями об'єктів, незалежних від замовлення, використовуйте набори / дикти / тощо. Це справді проблема XY, нам потрібно побачити більшу кодову базу, з якої вона складається. Я погоджуюсь, що це ((x == a and y == b) or (x == b and y == a))може виглядати юккі, але 1) його намір є кристально чистим і зрозумілим для всіх програмістів, що не належать до Python, а не криптовалюта. альтернативи. Отже, "більш елегантні" можуть мати і серйозні мінуси.
smci

Відповіді:


151

Якщо елементи є хешируемими, ви можете використовувати набори:

{a, b} == {y, x}

18
@Graham ні, це не так, тому що в правій руці встановлено рівно два предмети. Якщо обидва є a, немає b, і якщо обидва є b, немає a, і в будь-якому випадку множини не можуть бути рівними.
Hobbs

12
З іншого боку, якби у нас було три елементи на кожній стороні і нам потрібно було перевірити, чи можна їх узгодити один до одного, то набори не працювали б. {1, 1, 2} == {1, 2, 2}. У цей момент вам потрібно sortedабо Counter.
user2357112 підтримує Моніку

11
Мені здається, що це складно читати (не читайте "{}" як "()"). Досить прокоментувати це - і тоді мета втрачається.
Едуард

9
Я знаю, що це python, але створення двох нових наборів просто для порівняння значень здається мені надмірним ... Просто визначте функцію, яка виконує це, і зателефонуйте, коли потрібно.
marxin

5
@marxin A функція була б навіть більшою надмірністю, ніж 2 простих набору конструкцій. І менш читабельний з 4 аргументами.
Gloweye

60

Я думаю, що найкраще, що можна отримати, - це упакувати їх у кортежі:

if (a, b) == (x, y) or (a, b) == (y, x)

Або, можливо, оберніть це в наборі пошуку

if (a, b) in {(x, y), (y, x)}

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

from timeit import timeit

x = 1
y = 2
a = 3
b = 4

>>> timeit(lambda: (a, b) in {(x, y), (y, x)}, number=int(5e7))
32.8357742

>>> timeit(lambda: (a, b) in ((x, y), (y, x)), number=int(5e7))
31.6169182

Хоча кортежі насправді швидші, коли пошук успішний:

x = 1
y = 2
a = 1
b = 2

>>> timeit(lambda: (a, b) in {(x, y), (y, x)}, number=int(5e7))
35.6219458

>>> timeit(lambda: (a, b) in ((x, y), (y, x)), number=int(5e7))
27.753138700000008

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


12
Метод кортежу виглядає дуже чисто. Мене б заклопотало вплив на ефективність використання набору. Ти міг би зробити if (a, b) in ((x, y), (y, x)), хоча?
Brilliand

18
@Brilliand Якщо ви стурбовані впливом на продуктивність, Python не є для вас мовою. :-D
Sneftel

Мені дуже подобається перший метод, два порівняння кортежу. Легко проаналізувати (один за одним), і кожен пункт дуже простий. І понад цим, він повинен бути відносно ефективним.
Матьє М.

Чи є якась причина віддавати перевагу setрішенню у відповіді на рішення кортежу від @Brilliand?
користувач1717828

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

31

Кортежі роблять її трохи читабельнішою:

(x, y) == (a, b) or (x, y) == (b, a)

Це дає підказку: ми перевіряємо, чи послідовність x, yдорівнює послідовності, a, bале ігноруємо впорядкування. Це просто встановити рівність!

{x, y} == {a, b}

,створює кортеж, а не список. так (x, y)і (a, b)є кортежі, такі ж, як x, yі a, b.
kissgyorgy

Я мав на увазі "список" у значенні "упорядкована послідовність елементів", а не в значенні типу Python list. Відредаговано, бо справді це було заплутано.
Томас

27

Якщо елементи не доступні для зберігання, але підтримують порівняння для замовлення, ви можете спробувати:

sorted((x, y)) == sorted((a, b))

Оскільки це працює і з доступними предметами (правда?), Це більш глобальне рішення.
Карл Віттофт

5
@CarlWitthoft: Ні. Є типи, які є доступними, але не сортуються: complexнаприклад.
dan04

26

На мою думку, найелегантніший спосіб

(x, y) in ((a, b), (b, a))

Це кращий спосіб, ніж використання наборів, тобто {a, b} == {y, x}, як зазначено в інших відповідях, тому що нам не потрібно думати, чи є змінні хешируемими.


Чим це відрізняється від цієї попередньої відповіді ?
scohe001

5
@ scohe001 Він використовує кортеж, де в попередній відповіді використовується набір. Ця раніше відповідь розглядала це рішення, але відмовилася перераховувати його як рекомендацію.
Brilliand

25

Якщо це цифри, ви можете використовувати (x+y)==(a+b) and (x*y)==(a*b).

Якщо це порівнянні предмети, ви можете використовувати min(x,y)==min(a,b) and max(x,y)==max(a,b).

Але ((x == a and y == b) or (x == b and y == a))це зрозуміло, безпечно і загальніше.


2
Ха-ха, симетричні многочлени ftw!
Carsten S

2
Я думаю, що це створює ризик помилок переповнення.
Разван

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

3
@RazvanSocol OP не сказав, що це за типи даних, і ця відповідь кваліфікує рішення, що залежать від типу.
Z4-ярус

21

Як узагальнення більш ніж двох змінних ми можемо використовувати itertools.permutations. Це замість

(x == a and y == b and z == c) or (x == a and y == c and z == b) or ...

ми можемо писати

(x, y, z) in itertools.permutations([a, b, c])

І звичайно дві змінні версії:

(x, y) in itertools.permutations([a, b])

5
Чудова відповідь. Тут варто зазначити (для тих, хто раніше не робив багато з генераторами), що це дуже ефективно в пам'яті, оскільки створюється лише одна перестановка за один раз, і чек "in" зупиняється і повертає True відразу після відповідність знайдена.
Робертлейтон

2
Варто також зазначити, що цей метод є складним O(N*N!); Для 11 змінних це може зайняти більше секунди. (Я розмістив більш швидкий метод, але він все-таки займає O(N^2)і починає займати секунду на 10-ти змінних; тому, здається, це може бути зроблено швидко або взагалі (wrt. Hashability / orderability), але не обидва: P)
Aleksi Torhamo

15

Ви можете використовувати кортежі, щоб представити свої дані, а потім перевірити наявність включення, наприклад:

def test_fun(x, y):
    test_set = {(a, b), (b, a)}

    return (x, y) in test_set

3
Написаний як однолінійний перелік та зі списком (для предметів, які не є зручними), я вважаю це найкращою відповіддю. (хоча я і є повним новачком Python).
Édouard

1
@ Édouard Тепер є ще одна відповідь, що по суті є (лише з кортежем замість списку, який у будь-якому разі є більш ефективним).
Brilliand

10

Ви вже отримали найбільш читабельне рішення . Є й інші способи висловити це, можливо, з меншою кількістю символів, але вони менш зрозумілі для прямого перегляду.

Залежно від того, які значення насправді представляють вашу кращу ставку, це зафіксувати чек у функції з мовним іменем . Альтернативно або на додаток ви можете моделювати об'єкти x, y і a, b кожен із виділених об'єктів вищого класу, які ви потім можете порівняти з логікою порівняння методом перевірки рівності класів або виділеною спеціальною функцією.


3

Здається, що ОП стосувався лише випадку двох змінних, але оскільки StackOverflow також стосується тих, хто пізніше шукає те саме питання, я спробую тут детально розглянути загальний випадок; Одна попередня відповідь вже містить загальну відповідь із використанням itertools.permutations(), але цей метод призводить до O(N*N!)порівнянь, оскільки N!перестановки з Nелементами є кожен. (Це була основна мотивація цієї відповіді)

Спочатку давайте підсумуємо, як деякі методи у попередніх відповідях застосовуються до загального випадку, як мотивація методу, представленого тут. Я буду використовувати Aдля позначення (x, y)та Bдля позначення (a, b), які можуть бути кортежами довільної (але однакової) довжини.

set(A) == set(B)швидко, але працює лише в тому випадку, якщо значення є доступними, і ви можете гарантувати, що один з кортежів не містить жодних повторюваних значень. (Наприклад {1, 1, 2} == {1, 2, 2}, як вказував @ user2357112 під відповіддю @Daniel Mesejo)

Попередній метод може бути розширений для роботи з дублюючими значеннями, використовуючи словники з підрахунками, а не набори: (Це все ще має обмеження, що всі значення повинні бути доступними для перегляду, наприклад, змінні значення, як listне працюватимуть)

def counts(items):
    d = {}
    for item in items:
        d[item] = d.get(item, 0) + 1
    return d

counts(A) == counts(B)

sorted(A) == sorted(B)не вимагає змінних значень, але трохи повільніше, і вимагає замість цього впорядкованих значень. (Так, наприклад complex, не буде працювати)

A in itertools.permutations(B)не вимагає змінних або упорядкованих значень, але, як уже згадувалося, він має O(N*N!)складність, тому навіть із лише 11 предметами, це може зайняти більше секунди.

Отже, чи є спосіб бути настільки ж загальним, але робити це значно швидше? Чому так, "вручну" перевіряючи, чи є однакова кількість кожного елемента: (Складність цього полягає в O(N^2)тому, що це не добре і для великих входів; На моїй машині 10 к. Елементів можуть зайняти секунду - але з менші входи, як 10 елементів, це так само швидко, як і інші)

def unordered_eq(A, B):
    for a in A:
        if A.count(a) != B.count(a):
            return False
    return True

Щоб досягти найкращої продуктивності, можливо, dictспершу слід спробувати метод sorted-base, повернутися до countметоду -abased, якщо це не вдається через незмінні значення, і, нарешті, повернутися до методу-base, якщо це занадто не вдається через нерозбірливі значення.

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