Gibt es eine elegantere Möglichkeit, ((x == a und y == b) oder (x == b und y == a)) auszudrücken?

108

Ich versuche ((x == a and y == b) or (x == b and y == a))in Python zu evaluieren , aber es scheint ein bisschen ausführlich zu sein. Gibt es einen eleganteren Weg?

LetEpsilonBeLessThanZero
quelle
8
Hängt davon ab, um welche Art von Objekten x,y, a,bes sich handelt: Sind es Ints / Floats / Strings, beliebige Objekte oder was? Wenn sie builtin Typen waren und es war möglich , sowohl zu halten x,yund a,bin sortierter Reihenfolge, dann könnte man den zweiten Zweig zu vermeiden. Beachten Sie, dass beim Erstellen eines Satzes jedes der vier Elemente x,y, a,bgehasht wird, was trivial sein kann oder nicht oder Auswirkungen auf die Leistung hat, je nachdem, um welche Art von Objekten es sich handelt.
smci
18
Denken Sie an die Leute, mit denen Sie arbeiten, und an die Art von Projekt, das Sie entwickeln. Ich benutze hier und da ein wenig Python. Wenn jemand eine der Antworten hier codieren würde, müsste ich unbedingt googeln, was los ist. Ihre Bedingung ist so ziemlich lesbar, egal was passiert.
Areks
3
Ich würde mich nicht um Ersatz kümmern. Sie sind knapper, aber nicht so klar (IMHO) und ich denke, alle werden langsamer sein.
Barmar
22
Dies ist ein klassisches XYAB-Problem.
Tashus
5
Und im Allgemeinen, wenn Sie mit auftragsunabhängigen Sammlungen von Objekten arbeiten, verwenden Sie sets / dicts / etc. Dies ist wirklich ein XY-Problem. Wir müssten die größere Codebasis sehen, aus der es stammt. Ich bin damit einverstanden, dass das ((x == a and y == b) or (x == b and y == a))vielleicht yukky aussieht, aber 1) seine Absicht ist kristallklar und für alle Nicht-Python-Programmierer verständlich, nicht kryptisch. 2) Interpreten / Compiler werden immer gut damit umgehen und es kann im Gegensatz zu im Wesentlichen nie zu nicht performantem Code führen Alternativen. "Eleganter" kann also auch schwerwiegende Nachteile haben.
smci

Antworten:

151

Wenn die Elemente hashbar sind, können Sie Mengen verwenden:

{a, b} == {y, x}
Dani Mesejo
quelle
18
@Graham nein, tut es nicht, weil es genau zwei Elemente im rechten Satz gibt. Wenn beide a sind, gibt es kein b, und wenn beide b sind, gibt es kein a, und in beiden Fällen können die Mengen nicht gleich sein.
Hobbs
12
Wenn wir dagegen drei Elemente auf jeder Seite hätten und testen müssten, ob sie eins zu eins aufeinander abgestimmt werden könnten, würden Sets nicht funktionieren. {1, 1, 2} == {1, 2, 2}. An diesem Punkt brauchen Sie sortedoder Counter.
user2357112 unterstützt Monica
11
Ich finde es schwierig zu lesen (lesen Sie das "{}" nicht als "()"). Genug, um es zu kommentieren - und dann ist der Zweck verloren.
Édouard
9
Ich weiß, dass es Python ist, aber das Erstellen von zwei neuen Sets, nur um die Werte zu vergleichen, scheint mir ein Overkill zu sein ... Definieren Sie einfach eine Funktion, die dies tut, und rufen Sie bei Bedarf auf.
Marxin
5
@marxin Eine Funktion wäre sogar noch größer als 2 einfache Mengenkonstruktionen. Und mit 4 Argumenten weniger lesbar.
Gloweye
60

Ich denke, das Beste, was Sie bekommen können, ist, sie in Tupel zu verpacken:

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

Oder wickeln Sie das in eine Set-Lookup ein

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

Gerade als es in einigen Kommentaren erwähnt wurde, habe ich einige Timings durchgeführt, und Tupel und Sets scheinen hier identisch zu sein, wenn die Suche fehlschlägt:

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

Obwohl Tupel tatsächlich schneller sind, wenn die Suche erfolgreich ist:

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

Ich habe mich für die Verwendung eines Sets entschieden, weil ich eine Mitgliedersuche durchführe, und konzeptionell passt ein Set besser zu diesem Anwendungsfall als ein Tupel. Wenn Sie in einem bestimmten Anwendungsfall einen signifikanten Unterschied zwischen den beiden Strukturen gemessen haben, wählen Sie die schnellere. Ich denke jedoch nicht, dass Leistung hier ein Faktor ist.

Carcigenicate
quelle
12
Die Tupelmethode sieht sehr sauber aus. Ich wäre besorgt über die Auswirkungen der Verwendung eines Sets auf die Leistung. Sie könnten aber tun if (a, b) in ((x, y), (y, x))?
Brilliand
18
@Brilliand Wenn Sie sich Sorgen über die Auswirkungen auf die Leistung machen, ist Python nicht die richtige Sprache für Sie. :-D
Sneftel
Ich mag die erste Methode, zwei Tupelvergleiche. Es ist leicht zu analysieren (jeweils eine Klausel) und jede Klausel ist sehr einfach. Und obendrein sollte es relativ effizient sein.
Matthieu M.
Gibt es einen Grund, die setLösung in der Antwort der Tupellösung von @Brilliand vorzuziehen?
user1717828
Eine Menge erfordert, dass Objekte hashbar sind, daher wäre ein Tupel eine allgemeinere Lösung mit weniger Abhängigkeiten. Die Leistung ist zwar wahrscheinlich nicht allgemein wichtig, kann aber auch bei großen Objekten mit teuren Hashing- und Gleichheitsprüfungen sehr unterschiedlich aussehen.
Dukeling
31

Tupel machen es etwas lesbarer:

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

Dies gibt einen Hinweis: Wir prüfen, ob die Sequenz x, yder Sequenz entspricht a, b, ignorieren jedoch die Reihenfolge. Das ist nur Gleichheit!

{x, y} == {a, b}
Thomas
quelle
,erstellt ein Tupel, keine Liste. so (x, y)und (a, b)sind Tupel, wie x, yund a, b.
kissgyorgy
Ich meinte "Liste" im Sinne von "geordneter Folge von Elementen", nicht im Sinne des Python- listTyps. Bearbeitet, weil das in der Tat verwirrend war.
Thomas
27

Wenn die Elemente nicht hashbar sind, aber Bestellvergleiche unterstützen, können Sie Folgendes versuchen:

sorted((x, y)) == sorted((a, b))
jasonharper
quelle
Da dies auch mit hashbaren Elementen funktioniert (richtig?), Ist es eine globalere Lösung.
Carl Witthoft
5
@CarlWitthoft: Nein. Es gibt Typen, die hashbar, aber nicht sortierbar sind: complexZum Beispiel.
Dan04
26

Der eleganteste Weg wäre meiner Meinung nach

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

Dies ist ein besserer Weg als die Verwendung von Mengen, dh {a, b} == {y, x}wie in anderen Antworten angegeben, da wir nicht darüber nachdenken müssen, ob die Variablen hashbar sind.

Wagner Macedo
quelle
Wie unterscheidet sich das von dieser früheren Antwort ?
scohe001
5
@ scohe001 Es wird ein Tupel verwendet, bei dem die frühere Antwort eine Menge verwendet. Diese frühere Antwort berücksichtigte diese Lösung, lehnte es jedoch ab, sie als Empfehlung aufzulisten.
Brilliand
25

Wenn dies Zahlen sind, können Sie verwenden (x+y)==(a+b) and (x*y)==(a*b).

Wenn dies vergleichbare Artikel sind, können Sie verwenden min(x,y)==min(a,b) and max(x,y)==max(a,b).

Ist ((x == a and y == b) or (x == b and y == a))aber klar, sicher und allgemeiner.

lhf
quelle
2
Haha, symmetrische Polynome ftw!
Carsten S
2
Ich denke, dies birgt das Risiko von Überlauffehlern.
Razvan Socol
3
Dies ist die richtige Antwort, insbesondere der letzte Satz. Halten Sie es einfach, dies sollte nicht mehrere neue Iterables oder ähnliches erfordern. Für diejenigen, die Sets verwenden möchten, werfen Sie einen Blick auf die Implementierung von Set-Objekten und stellen Sie sich dann vor, Sie versuchen, dies in einer engen Schleife auszuführen ...
Z4-Tier
3
@RazvanSocol OP hat die Datentypen nicht angegeben, und diese Antwort qualifiziert die typabhängigen Lösungen.
Z4-Tier
21

Als Verallgemeinerung auf mehr als zwei Variablen können wir verwenden itertools.permutations. Das ist statt

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

wir können schreiben

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

Und natürlich die zwei variablen Versionen:

(x, y) in itertools.permutations([a, b])
ein Gast
quelle
5
Gute Antwort. Hier ist darauf hinzuweisen (für diejenigen, die zuvor noch nicht viel mit Generatoren gemacht haben), dass dies sehr speichereffizient ist, da jeweils nur eine Permutation erstellt wird und die "In" -Prüfung sofort nach a stoppt und True zurückgibt Übereinstimmung gefunden.
Robertlayton
2
Es ist auch erwähnenswert, dass die Komplexität dieser Methode ist O(N*N!); Bei 11 Variablen kann dies eine Sekunde dauern. (Ich habe eine schnellere Methode veröffentlicht, aber sie dauert immer noch O(N^2)und beginnt mit einer Sekunde für 10.000 Variablen. Es scheint also, dass dies entweder schnell oder allgemein (in Bezug auf Hashbarkeit / Bestellbarkeit) möglich ist, aber nicht beides: P)
Aleksi Torhamo
15

Sie können Tupel verwenden, um Ihre Daten darzustellen, und dann prüfen, ob sie festgelegt sind, z.

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

    return (x, y) in test_set
Nils Müller
quelle
3
Als Einzeiler und mit einer Liste (für nicht hashbare Elemente) geschrieben, würde ich dies als die beste Antwort betrachten. (obwohl ich ein kompletter Python-Neuling bin).
Édouard
1
@ Édouard Nun gibt es eine andere Antwort, die im Wesentlichen so ist (nur mit einem Tupel anstelle einer Liste, die sowieso effizienter ist).
Brilliand
10

Sie haben bereits die am besten lesbare Lösung . Es gibt andere Möglichkeiten, dies auszudrücken, vielleicht mit weniger Zeichen, aber sie sind weniger einfach zu lesen.

Abhängig davon, was die Werte tatsächlich darstellen, ist es am besten, den Scheck in eine Funktion mit einem sprechenden Namen einzuwickeln . Alternativ oder zusätzlich können Sie die Objekte x, y und a, b jeweils in dedizierten Objekten höherer Klasse modellieren, die Sie dann mit der Logik des Vergleichs in einer Methode zur Überprüfung der Klassengleichheit oder einer dedizierten benutzerdefinierten Funktion vergleichen können.

Frank Hopkins
quelle
3

Es scheint, dass sich das OP nur mit dem Fall von zwei Variablen befasst hat, aber da StackOverflow auch für diejenigen gedacht ist, die später nach derselben Frage suchen, werde ich versuchen, den allgemeinen Fall hier etwas detaillierter zu behandeln. Eine vorherige Antwort enthält bereits eine generische Antwort mit itertools.permutations(), aber diese Methode führt zu O(N*N!)Vergleichen, da es N!Permutationen mit jeweils NElementen gibt. (Dies war die Hauptmotivation für diese Antwort)

Lassen Sie uns zunächst zusammenfassen, wie einige der Methoden in früheren Antworten auf den generischen Fall als Motivation für die hier vorgestellte Methode zutreffen. Ich werde verwenden, um auf Azu verweisen (x, y)und auf Bzu verweisen (a, b), was Tupel beliebiger (aber gleicher) Länge sein können.

set(A) == set(B)ist schnell, funktioniert aber nur, wenn die Werte hashbar sind und Sie garantieren können, dass eines der Tupel keine doppelten Werte enthält. (ZB {1, 1, 2} == {1, 2, 2}wie von @ user2357112 unter @ Daniel Mesejos Antwort hervorgehoben)

Die vorherige Methode kann erweitert werden, um mit doppelten Werten zu arbeiten, indem Wörterbücher mit Zählungen anstelle von Mengen verwendet werden: (Dies hat immer noch die Einschränkung, dass alle Werte hashbar sein müssen, z. B. veränderbare Werte wie listfunktionieren nicht.)

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)erfordert keine hashbaren Werte, ist jedoch etwas langsamer und erfordert stattdessen bestellbare Werte. (Also zB complexnicht funktionieren)

A in itertools.permutations(B)Es sind keine hashbaren oder bestellbaren Werte erforderlich, aber wie bereits erwähnt, ist es komplex O(N*N!), sodass selbst bei nur 11 Elementen die Fertigstellung über eine Sekunde dauern kann.

Gibt es also eine Möglichkeit, so allgemein zu sein, aber geht es erheblich schneller? Warum ja, indem "manuell" überprüft wird, ob von jedem Element die gleiche Menge vorhanden ist: (Die Komplexität dieses Elements ist O(N^2)so, dass dies auch für große Eingaben nicht gut ist. Auf meinem Computer können 10.000 Elemente eine Sekunde dauern - aber mit kleinere Eingaben, wie 10 Elemente, dies ist genauso schnell wie die anderen)

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

Um die beste Leistung zu erzielen, sollten Sie dictzuerst die sortedMethode -based ausprobieren, auf die -based-Methode zurückgreifen, wenn dies aufgrund nicht zerlegbarer Werte fehlschlägt, und schließlich auf die count-based-Methode zurückgreifen, wenn auch dies aufgrund nicht ordnbarer Werte fehlschlägt.

Aleksi Torhamo
quelle