Czy istnieje bardziej elegancki sposób wyrażenia ((x == ay y == b) lub (x == bi y == a))?

108

Próbuję oceniać ((x == a and y == b) or (x == b and y == a))w Pythonie, ale wydaje się to trochę gadatliwe. Czy istnieje bardziej elegancki sposób?

LetEpsilonBeLessThanZero
źródło
8
Zależy od tego, jakie są typy obiektów x,y, a,b: czy są to ints / float / strings, dowolne obiekty, czy co? Gdyby były wbudowane typy i udało się zachować zarówno x,yi a,bposortowanych, wtedy można uniknąć drugiego oddziału. Zauważ, że utworzenie zestawu spowoduje, że każdy z czterech elementów x,y, a,bzostanie zaszyfrowany, co może, ale nie musi, być trywialne lub mieć wpływ na wydajność w zależności od tego, jakiego rodzaju są obiektami.
smci
18
Pamiętaj o ludziach, z którymi współpracujesz i rodzaju projektu, który opracowujesz. Tu i tam używam małego Pythona. Gdyby ktoś zakodował jedną z odpowiedzi tutaj, musiałbym całkowicie google, co się dzieje. Twój warunek jest czytelny praktycznie bez względu na wszystko.
Areks
3
Nie zawracałbym sobie głowy żadnym z zamienników. Są bardziej zwięzłe, ale nie tak jasne (IMHO) i myślę, że wszystko będzie wolniejsze.
Barmar
22
To klasyczny problem XYAB.
Tashus
5
I ogólnie, jeśli masz do czynienia z kolekcjami obiektów niezależnymi od porządku, użyj zestawów / dicts / etc. To naprawdę jest problem XY, musielibyśmy zobaczyć większą bazę kodów, z której został pobrany. Zgadzam się, że ((x == a and y == b) or (x == b and y == a))może to wyglądać dziwacznie, ale 1) jego intencja jest krystalicznie czysta i zrozumiała dla wszystkich programistów nie-Pythona, a nie kryptycznych 2) interpretery / kompilatory zawsze dobrze sobie z tym poradzą i zasadniczo nie może skutkować niewykonalnym kodem, w przeciwieństwie do alternatywy Tak więc „bardziej elegancki” może mieć również poważne wady.
smci,

Odpowiedzi:

151

Jeśli elementy są haszowalne, możesz użyć zestawów:

{a, b} == {y, x}
Dani Mesejo
źródło
18
@Graham nie, nie ma, ponieważ w zestawie prawej ręki znajdują się dokładnie dwa przedmioty. Jeśli oba są a, nie ma b, a jeśli oba są b, nie ma a, w obu przypadkach zbiory nie mogą być równe.
hobbs
12
Z drugiej strony, gdybyśmy mieli trzy elementy po każdej stronie i musieliśmy sprawdzić, czy można je dopasować jeden do jednego, wtedy zestawy nie działałyby. {1, 1, 2} == {1, 2, 2}. W tym momencie potrzebujesz sortedlub Counter.
user2357112 obsługuje Monikę
11
Uważam to za trudne do odczytania (nie czytaj „{}” jako „()”). Wystarczy, aby to skomentować - a wtedy cel zostanie utracony.
Édouard,
9
Wiem, że to python, ale tworzenie dwóch nowych zestawów tylko w celu porównania wartości wydaje mi się przesadą ... Po prostu zdefiniuj funkcję, która to robi i wywołuje w razie potrzeby.
marxin
5
@marxin Funkcja byłaby jeszcze większa nadwyżka niż 2 proste konstrukcje zestawów. I mniej czytelny z 4 argumentami.
Gloweye
60

Myślę, że najlepsze, co możesz dostać, to spakować je w krotki:

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

A może zawiń to w zestaw wyszukiwania

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

Właśnie dlatego, że zostało to wspomniane w kilku komentarzach, zrobiłem pewne czasy, a krotki i zestawy wydają się działać tutaj identycznie, gdy wyszukiwanie nie powiedzie się:

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

Chociaż krotki są w rzeczywistości szybsze, gdy wyszukiwanie się powiedzie:

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

Zdecydowałem się użyć zestawu, ponieważ przeprowadzam wyszukiwanie członkostwa, a koncepcyjnie zestaw lepiej pasuje do tego przypadku użycia niż krotka. Jeśli zmierzyłeś znaczącą różnicę między dwiema strukturami w konkretnym przypadku użycia, przejdź do szybszej. Nie sądzę jednak, żeby wydajność była tutaj czynnikiem.

Carcigenicate
źródło
12
Metoda krotki wygląda bardzo czysto. Byłbym zaniepokojony wpływem używania zestawu na wydajność. Mógłbyś jednak zrobić if (a, b) in ((x, y), (y, x))?
Brilliand
18
@Brilliand Jeśli martwisz się wpływem na wydajność, Python nie jest dla Ciebie językiem. :-D
Sneftel
Naprawdę podoba mi się pierwsza metoda, dwa porównania krotek. Jest łatwy do przeanalizowania (jedna klauzula na raz), a każda klauzula jest bardzo prosta. A do tego powinien być stosunkowo wydajny.
Matthieu M.,
Czy jest jakiś powód, aby preferować setrozwiązanie w odpowiedzi na rozwiązanie krotki z @Brilliand?
user1717828,
Zestaw wymaga, aby obiekty były haszowalne, więc krotka byłaby bardziej ogólnym rozwiązaniem z mniejszą liczbą zależności. Wydajność, choć prawdopodobnie nie jest generalnie ważna, może również wyglądać zupełnie inaczej w przypadku dużych obiektów z kosztownymi wartościami mieszania i kontroli równości.
Dukeling,
31

Krotki sprawiają, że jest nieco bardziej czytelny:

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

To daje wskazówkę: sprawdzamy, czy sekwencja x, yjest równa sekwencji, a, bale ignorujemy kolejność. To tylko równość!

{x, y} == {a, b}
Tomasz
źródło
,tworzy krotkę, a nie listę. tak (x, y)i (a, b)to krotki, tak samo jak x, yi a, b.
kissgyorgy
Miałem na myśli „listę” w znaczeniu „uporządkowanej sekwencji elementów”, a nie w znaczeniu listtypu Python . Edytowane, ponieważ rzeczywiście było to mylące.
Thomas
27

Jeśli elementy nie są haszowalne, ale obsługują porównywanie zamówień, możesz spróbować:

sorted((x, y)) == sorted((a, b))
Jasonharper
źródło
Ponieważ działa to również z elementami haszującymi (prawda?), Jest to rozwiązanie bardziej globalne.
Carl Witthoft
5
@CarlWitthoft: Nie. Istnieją typy, które można mieszać, ale nie można ich sortować: complexna przykład.
dan04
26

Moim zdaniem byłby to najbardziej elegancki sposób

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

Jest to lepszy sposób niż używanie zbiorów, tj. {a, b} == {y, x}Jak wskazano w innych odpowiedziach, ponieważ nie musimy myśleć, czy zmienne są możliwe do skrócenia.

Wagner Macedo
źródło
Czym różni się to od wcześniejszej odpowiedzi ?
scohe001,
5
@ scohe001 Używa krotki, w której wcześniejsza odpowiedź używa zestawu. Ta wcześniejsza odpowiedź dotyczyła tego rozwiązania, ale odmówiła podania go jako zalecenia.
Brilliand
25

Jeśli są to liczby, możesz użyć (x+y)==(a+b) and (x*y)==(a*b) .

Jeśli są to porównywalne przedmioty, możesz użyć min(x,y)==min(a,b) and max(x,y)==max(a,b) .

Ale ((x == a and y == b) or (x == b and y == a))jest jasne, bezpieczne i bardziej ogólne.

lhf
źródło
2
Haha, symetryczne wielomiany ftw!
Carsten S
2
Myślę, że stwarza to ryzyko błędów przepełnienia.
Razvan Socol
3
To poprawna odpowiedź, w szczególności ostatnie zdanie. Upraszczając, nie powinno to wymagać wielu nowych iteracji ani nic podobnego. Dla tych, którzy chcą korzystać z zestawów, spójrz na implementację zestawów obiektów, a następnie wyobraź sobie próbę uruchomienia tego w ciasnej pętli ...
Z4-tier
3
@RazvanSocol OP nie powiedział, jakie są typy danych, a ta odpowiedź kwalifikuje rozwiązania zależne od typu.
Z4-tier
21

Jako uogólnienie na więcej niż dwie zmienne możemy użyć itertools.permutations. To jest zamiast

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

możemy pisać

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

I oczywiście dwie zmienne wersje:

(x, y) in itertools.permutations([a, b])
gość
źródło
5
Świetna odpowiedź. Warto tutaj wskazać (dla tych, którzy wcześniej niewiele robili z generatorami), że jest to bardzo wydajne pod względem pamięci, ponieważ tworzona jest tylko jedna permutacja na raz, a kontrola „in” zatrzyma się i zwróci True natychmiast po dopasowanie zostało znalezione.
robertlayton,
2
Warto również zauważyć, że złożoność tej metody jest O(N*N!); W przypadku 11 zmiennych może to zająć ponad sekundę. (Opublikowałem szybszą metodę, ale wciąż trwa O(N^2)i zaczyna przejmować sekundę na 10k zmiennych; Wygląda więc na to, że można to zrobić szybko lub ogólnie (wrt. Hashability / orderability), ale nie oba: P)
Aleksi Torhamo
15

Możesz użyć krotek do przedstawienia swoich danych, a następnie sprawdzić włączenie zestawu, na przykład:

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

    return (x, y) in test_set
Nils Müller
źródło
3
Napisane jako linijka z listą (w przypadku przedmiotów, których nie można haszować), uznałbym to za najlepszą odpowiedź. (chociaż jestem kompletnym nowicjuszem w Pythonie).
Édouard,
1
@ Édouard Teraz jest inna odpowiedź, która jest w istocie taka (tylko z krotką zamiast listy, która i tak jest bardziej wydajna).
Brilliand
10

Masz już najbardziej czytelne rozwiązanie . Istnieją inne sposoby wyrażenia tego, być może przy użyciu mniejszej liczby znaków, ale są one mniej czytelne.

W zależności od tego, jakie wartości faktycznie reprezentują twój najlepszy zakład, należy zawrzeć czek w funkcji wypowiedzianą nazwą . Alternatywnie lub dodatkowo można modelować obiekty x, y i a, b w dedykowanych obiektach wyższej klasy, które następnie można porównać z logiką porównania w metodzie sprawdzania równości klas lub dedykowanej funkcji niestandardowej.

Frank Hopkins
źródło
3

Wygląda na to, że OP zajmował się jedynie przypadkiem dwóch zmiennych, ale ponieważ StackOverflow jest również dla tych, którzy szukają tego samego pytania później, postaram się tutaj szczegółowo zająć przypadkiem ogólnym; Jedna poprzednia odpowiedź zawiera już ogólną odpowiedź itertools.permutations(), ale ta metoda prowadzi do O(N*N!)porównań, ponieważ istnieją N!permutacje z Nelementami. (To była główna motywacja tej odpowiedzi)

Najpierw podsumujmy, w jaki sposób niektóre metody z poprzednich odpowiedzi odnoszą się do przypadku ogólnego, jako motywację do metody przedstawionej tutaj. Będę używać Ado odwoływania się (x, y)i Bdo odniesienia (a, b), które mogą być krotkami o dowolnej (ale równej) długości.

set(A) == set(B)jest szybki, ale działa tylko wtedy, gdy wartości są haszowalne i możesz zagwarantować, że jedna z krotek nie zawiera żadnych zduplikowanych wartości. (Na przykład.{1, 1, 2} == {1, 2, 2} , Jak wskazał @ user2357112 w odpowiedzi @Daniel Mesejo)

Poprzednia metoda może zostać rozszerzona do pracy ze zduplikowanymi wartościami za pomocą słowników z licznikami zamiast zestawów: (To wciąż ma ograniczenie, że wszystkie wartości muszą być możliwe do skrótu, więc np. Zmienne wartości takie jak listnie będą działać)

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)nie wymaga wartości mieszających, ale jest nieco wolniejszy i zamiast tego wymaga wartości uporządkowanych. (Więc np. complexNie będzie działać)

A in itertools.permutations(B)nie wymaga wartości skrótu ani zamówień, ale jak już wspomniano, ma O(N*N!)złożoność, więc nawet przy 11 elementach ukończenie może zająć sekundę.

Czy istnieje sposób, aby być tak ogólnym, ale czy jest to znacznie szybsze? Dlaczego tak, „ręcznie” sprawdzając, czy jest taka sama ilość każdego elementu: (Złożoność tego jest O(N^2)taka, że ​​nie jest to również dobre w przypadku dużych nakładów; na mojej maszynie 10 000 elementów może zająć sekundę - ale z mniejsze nakłady, takie jak 10 pozycji, jest tak samo szybki jak inne)

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

Aby uzyskać najlepszą wydajność, można dictnajpierw wypróbować metodę opartą na bazie , powrócić do sortedmetody opartej na bazie, jeśli to się nie powiedzie z powodu nieusuwalnych wartości, a na koniec wrócić do countmetody opartej na podstawie , jeśli to też się nie powiedzie z powodu nieuporządkowanych wartości.

Aleksi Torhamo
źródło