Есть ли более элегантный способ выражения ((x == a и y == b) или (x == b и y == a))?

108

Я пытаюсь оценить ((x == a and y == b) or (x == b and y == a))в Python, но это кажется немного многословным. Есть ли более элегантный способ?

LetEpsilonBeLessThanZero
источник
8
Зависит от типа объектов x,y, a,b: это целые / плавающие / строковые объекты, произвольные объекты или что? Если бы они были встроенными типами, и было возможно сохранить оба x,yи a,bв отсортированном порядке, то вы могли бы избежать второй ветви. Обратите внимание, что создание набора приведет x,y, a,bк хешированию каждого из четырех элементов , что может быть или не быть тривиальным или влиять на производительность в зависимости от того, к какому типу объектов они относятся.
SMCI
18
Помните о людях, с которыми вы работаете, и о типе проекта, который вы разрабатываете. Я использую немного Python здесь и там. Если бы кто-то закодировал один из ответов здесь, мне бы пришлось погуглить, что происходит. Ваш условный текст читаем в значительной степени, несмотря ни на что.
Арекс
3
Я не стал бы беспокоиться ни о какой замене. Они более лаконичны, но не так ясны (ИМХО), и я думаю, что все будет медленнее.
Бармар
22
Это классическая проблема XYAB.
Ташус
5
И вообще, если вы имеете дело с независимыми от порядка коллекциями объектов, используйте sets / dicts / etc. Это действительно проблема XY, нам нужно увидеть большую кодовую базу, из которой она взята. Я согласен, что это ((x == a and y == b) or (x == b and y == a))может выглядеть юкки, но 1) его цель кристально ясна и понятна всем не-Python-программистам, а не загадочным 2) интерпретаторы / компиляторы всегда с этим справятся хорошо, и это, по сути, никогда не приведет к неэффективному коду, в отличие от альтернативы. Таким образом, у «более элегантных» тоже могут быть серьезные недостатки.
SMCI

Ответы:

151

Если элементы являются хэшируемыми, вы можете использовать наборы:

{a, b} == {y, x}
Дани Месехо
источник
18
@ Грэм нет, это не так, потому что в правой руке есть ровно два предмета. Если оба являются a, нет b, и если оба b, нет a, и в любом случае наборы не могут быть равными.
Хоббс
12
С другой стороны, если у нас было три элемента на каждой стороне, и нам нужно было проверить, могут ли они быть сопоставлены один к одному, то наборы не будут работать. {1, 1, 2} == {1, 2, 2}, В этот момент вам нужно sortedили Counter.
user2357112 поддерживает Monica
11
Я считаю, что это сложно читать (не читайте "{}" как "()"). Достаточно прокомментировать это - и тогда цель потеряна.
Эдуард
9
Я знаю, что это python, но создание двух новых наборов только для сравнения значений кажется мне излишним ... Просто определите функцию, которая делает это и вызывает при необходимости.
Марксин
5
@marxin Функция будет даже больше, чем две простые конструкции множеств. И менее читабелен с 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

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

Carcigenicate
источник
12
Метод кортежа выглядит очень чистым. Я был бы обеспокоен влиянием на производительность использования набора. Вы могли бы сделать if (a, b) in ((x, y), (y, x)), хотя?
Brilliand
18
@ Brilliand Если вы беспокоитесь о влиянии на производительность, Python не для вас. :-D
Sneftel
Мне очень нравится первый метод, два сравнения кортежей. Это легко разобрать (по одному предложению за раз), и каждое предложение очень просто. И к тому же, он должен быть относительно эффективным.
Матье М.
Есть ли причина предпочитать setрешение в ответе решению кортежа из @Brilliand?
user1717828
Набор требует, чтобы объекты были хэшируемыми, поэтому кортеж был бы более общим решением с меньшим количеством зависимостей. Производительность, хотя, возможно, и не важна, может также выглядеть очень по-разному при работе с большими объектами с дорогостоящими проверками хеширования и равенства.
Dukeling
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. Отредактировано, потому что действительно это сбивало с толку.
Томас
26

Если элементы не могут быть хешируемыми, но поддерживают сравнения при сортировке, вы можете попробовать:

sorted((x, y)) == sorted((a, b))
jasonharper
источник
Поскольку это работает и с хэшируемыми элементами (верно?), Это более глобальное решение.
Карл Виттофт
5
@CarlWitthoft: Нет. Есть типы, которые можно хэшировать, но не сортировать complex, например.
Ден04
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))это ясно, безопасно и более общее.

LHF
источник
2
Ха-ха, симметричные многочлены ftw!
Карстен 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)и начинает занимать более секунды для переменных 10k; так что, похоже, это можно сделать быстро или в целом (относительно хешабельности / упорядоченности), но не одновременно: P)
Алекси Торхамо
15

Вы можете использовать кортежи для представления своих данных, а затем проверить их на включение, например:

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

    return (x, y) in test_set
Нильс Мюллер
источник
3
Написанный как однострочный и со списком (для не хэшируемых элементов), я считаю, что это лучший ответ. (хотя я полный новичок в Python).
Эдуард
1
@ Édouard Теперь есть еще один ответ, который по существу таков (просто с кортежем вместо списка, который в любом случае более эффективен).
Brilliand
10

Вы уже получили самое читаемое решение . Есть и другие способы выразить это, возможно, с меньшим количеством символов, но они не так просты для чтения.

В зависимости от того, какие значения на самом деле представляют вашу лучшую ставку, стоит обернуть чек в функцию с говорящим именем . В качестве альтернативы или дополнения вы можете моделировать объекты x, y и a, b каждый в выделенных объектах более высокого класса, которые затем можно сравнить с логикой сравнения в методе проверки равенства классов или в специальной пользовательской функции.

Фрэнк Хопкинс
источник
3

Кажется, что OP касался только случая двух переменных, но, поскольку 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-based, вернуться к countметоду -base, если это не удастся из-за необратимых значений, и, наконец, вернуться к методу -based, если он тоже не удастся из-за неупорядоченных значений.

Алекси Торхамо
источник