Wie kann ich den fehlenden Wert präziser finden?

76

Der folgende Code überprüft , ob xund ysind verschiedene Werte (die Variablen x, y, zkönnen nur Werte a, boder c) und wenn ja, setzt zauf das dritte Zeichen:

if x == 'a' and y == 'b' or x == 'b' and y == 'a':
    z = 'c'
elif x == 'b' and y == 'c' or x == 'c' and y == 'b':
    z = 'a'
elif x == 'a' and y == 'c' or x == 'c' and y == 'a':
    z = 'b'

Ist dies möglich, präziser, lesbarer und effizienter?

Häschen
quelle
6
Die kurze Antwort lautet "Ja!" Pythons Sets eignen sich hervorragend zur Überprüfung der Unterscheidbarkeit und zur Berechnung nicht verwendeter Elemente.
Raymond Hettinger
1
Vielen Dank für die Antworten. Ich denke, ich werde die Lösung mit set als relativ schnell und lesbar verwenden. Die auf der Nachschlagetabelle basierende Antwort von Óscar López ist ebenfalls interessant.
Bunny Rabbit

Antworten:

62
z = (set(("a", "b", "c")) - set((x, y))).pop()

Ich gehe davon aus, dass einer der drei Fälle in Ihrem Code gilt. In diesem Fall besteht die Menge set(("a", "b", "c")) - set((x, y))aus einem einzelnen Element, das von zurückgegeben wird pop().

Bearbeiten: Wie von Raymond Hettinger in den Kommentaren vorgeschlagen, können Sie auch das Entpacken von Tupeln verwenden, um das einzelne Element aus der Menge zu extrahieren:

z, = set(("a", "b", "c")) - set((x, y))
Sven Marnach
quelle
26
Wenn Sie Python 2.7 / 3.1 oder höher verwenden, können Sie es mit festgelegten Literalen noch präziser schreiben:z = ({'a', 'b', 'c'} - {x, y}).pop()
Taymon
7
Das pop () ist unnötig und langsam. Verwenden Sie stattdessen das Auspacken von Tupeln. Außerdem ist das set(("a", "b", "c"))invariant, sodass es einmal vorberechnet werden kann, sodass nur die eingestellte Differenz in einer Schleife verwendet werden kann (wenn es nicht in einer Schleife verwendet wird, ist uns die Geschwindigkeit weniger wichtig).
Raymond Hettinger
3
@Ed: Ich weiß, aber das OP hat nicht angegeben, was wann zu tun ist x == y, deshalb habe ich den Test weggelassen. Es ist einfach genug, if x != y:bei Bedarf hinzuzufügen .
Sven Marnach
4
Persönlich würde ich mich für das erste entscheiden, da es offensichtlicher ist als ein zufälliges Komma.
John
1
Sie können ersetzen wollen set(("a", "b", "c"))durch set("abc").
Kasyc
47

Die stripMethode ist eine weitere Option, die für mich schnell ausgeführt wird:

z = 'abc'.strip(x+y) if x!=y else None
chepner
quelle
2
+1 Es ist auch sehr transparent und im Gegensatz zu den meisten Antworten handelt es sich um x == y.
Ed Staub
1
Gute Idee, +1; obwohl ich glaube tatsächlich , dass "a", "b"und "c"in der ursprünglichen Nachricht sind nur Platzhalter für die wirklichen Werte. Diese Lösung lässt sich nicht auf einen anderen Werttyp als Zeichenfolgen der Länge 1 verallgemeinern.
Sven Marnach
@chepner das ist kreativ! Danke, dass du chepner geantwortet hast.
Bunny Rabbit
27

Svens exzellenter Code hat nur ein wenig zu viel Arbeit geleistet und hätte Tupel-Entpacken anstelle von Pop () verwenden sollen . Es hätte auch einen Schutz hinzufügen können, um if x != yzu überprüfen, ob x und y unterschiedlich sind. So sieht die verbesserte Antwort aus:

# create the set just once
choices = {'a', 'b', 'c'}

x = 'a'
y = 'b'

# the main code can be used in a loop
if x != y:
    z, = choices - {x, y}

Hier sind die vergleichenden Timings mit einer Timing-Suite, um die relative Leistung zu zeigen:

import timeit, itertools

setup_template = '''
x = %r
y = %r
choices = {'a', 'b', 'c'}
'''

new_version = '''
if x != y:
    z, = choices - {x, y}
'''

original_version = '''
if x == 'a' and y == 'b' or x == 'b' and y == 'a':
    z = 'c'
elif x == 'b' and y == 'c' or x == 'c' and y == 'b':
    z = 'a'
elif x == 'a' and y == 'c' or x == 'c' and y == 'a':
    z = 'b'
'''

for x, y in itertools.product('abc', repeat=2):
    print '\nTesting with x=%r and y=%r' % (x, y)
    setup = setup_template % (x, y)
    for stmt, name in zip([original_version, new_version], ['if', 'set']):
        print min(timeit.Timer(stmt, setup).repeat(7, 100000)),
        print '\t%s_version' % name

Hier sind die Ergebnisse der Timings:

Testing with x='a' and y='a'
0.0410830974579     original_version
0.00535297393799    new_version

Testing with x='a' and y='b'
0.0112571716309     original_version
0.0524711608887     new_version

Testing with x='a' and y='c'
0.0383319854736     original_version
0.048309803009      new_version

Testing with x='b' and y='a'
0.0175108909607     original_version
0.0508949756622     new_version

Testing with x='b' and y='b'
0.0386209487915     original_version
0.00529098510742    new_version

Testing with x='b' and y='c'
0.0259420871735     original_version
0.0472128391266     new_version

Testing with x='c' and y='a'
0.0423510074615     original_version
0.0481910705566     new_version

Testing with x='c' and y='b'
0.0295209884644     original_version
0.0478219985962     new_version

Testing with x='c' and y='c'
0.0383579730988     original_version
0.00530385971069    new_version

Diese Timings zeigen, dass die Leistung der Originalversion erheblich variiert, je nachdem, welche if-Anweisungen durch die verschiedenen Eingabewerte ausgelöst werden.

Raymond Hettinger
quelle
2
Ihr Test scheint voreingenommen zu sein. Die sogenannte "set_version" ist nur manchmal schneller, weil sie durch eine zusätzliche ifAnweisung geschützt wird .
Ekhumoro
2
@ekhumoro Das war es, was die Problemspezifikation forderte: "prüft, ob x und y unterschiedliche Werte sind und setzt in diesem Fall z auf das dritte Zeichen". Der schnellste (und einfachste) Weg, um zu überprüfen, ob Werte unterschiedlich sind, ist x != y. Nur wenn sie verschieden sind, bestimmen wir den Satzunterschied, um das dritte Zeichen zu bestimmen :-)
Raymond Hettinger
2
Der Punkt, den ich angesprochen habe, ist, dass Ihre Tests nicht zeigen, dass die Leistung set_versionbesser ist, weil sie auf Mengen basiert . es funktioniert nur aufgrund der Schutzanweisung besser if.
Ekhumoro
1
@ekhumoro Das ist eine seltsame Lektüre der Testergebnisse. Der Code macht das, wonach das OP gefragt hat. Die Timings zeigen die Vergleichsleistung mit allen möglichen Gruppen von Eingaben. Es liegt an Ihnen, wie Sie diese interpretieren möchten. Die Zeiten für die Version mit if x != y: z, = choices - {x, y}Tarifen sind im Vergleich zum Originalcode des OP recht gut. Ich weiß nicht, woher Ihre Vorstellung von Voreingenommenheit kommt - die Timings sind so, wie sie sind, und AFAICT, dies ist immer noch die beste der Antworten, die veröffentlicht wurden. Es ist sowohl sauber als auch schnell.
Raymond Hettinger
2
Svens "Set-Version" wurde um mehrere Optimierungen erweitert, die jedoch nicht für die "If-Version" durchgeführt wurden. Das Hinzufügen eines if x != ySchutzes zur "if-Version" würde sie wahrscheinlich konsistenter und leistungsfähiger machen als alle anderen bisher angebotenen Lösungen (obwohl offensichtlich nicht so lesbar und präzise). Ihre "set_version" ist eine sehr gute Lösung - es ist einfach nicht ganz so gut, wie die Tests es scheinen lassen ;-)
ekhumoro
18
z = (set('abc') - set(x + y)).pop()

Hier sind alle Szenarien, um zu zeigen, dass es funktioniert:

>>> (set('abc') - set('ab')).pop()   # x is a/b and y is b/a
'c'
>>> (set('abc') - set('bc')).pop()   # x is b/c and y is c/b
'a'
>>> (set('abc') - set('ac')).pop()   # x is a/c and y is c/a
'b'
Andrew Clark
quelle
15

Wenn die drei Elemente in Frage nicht waren "a", "b"und "c", sondern 1, 2und 3könnten Sie auch eine binäre XOR verwenden:

z = x ^ y

Wenn Sie ganz allgemein festlegen möchten zauf die verbleibende eine von drei Zahlen a, bund czwei Zahlen angegeben xund yaus diesem Set können Sie verwenden

z = x ^ y ^ a ^ b ^ c

Natürlich können Sie vorberechnen, a ^ b ^ cwenn die Zahlen fest sind.

Dieser Ansatz kann auch mit den Originalbuchstaben verwendet werden:

z = chr(ord(x) ^ ord(y) ^ 96)

Beispiel:

>>> chr(ord("a") ^ ord("c") ^ 96)
'b'

Erwarten Sie nicht, dass jemand, der diesen Code liest, sofort herausfindet, was er bedeutet :)

Sven Marnach
quelle
+1 Diese Lösung scheint schön und elegant zu sein. und wenn Sie es zu einer eigenen Funktion machen und der magischen Zahl 96 einen Namen geben, ist die Logik ziemlich einfach zu folgen / zu pflegen ( xor_of_a_b_c = 96 # ord('a') ^ ord('b') ^ ord('c') == 96). In Bezug auf die Rohgeschwindigkeit ist dies jedoch etwa 33% langsamer als die lange Kette von if / elifs; aber 500% schneller als die setMethode.
Dr. Jimbob
@sven Danke, dass Sie mir den XOR-Operator vorgestellt haben. Ihre Lösung ist sauber und elegant. Ich denke, dieses Beispiel wird es an meinem Gehirn festhalten. Nochmals vielen Dank :)
Bunny Rabbit
13

Ich finde die Lösung von Sven Marnach und FJ wunderschön, aber in meinem kleinen Test nicht schneller. Dies ist die optimierte Version von Raymond, die eine vorberechnete Version verwendet set:

$ python -m timeit -s "choices = set('abc')" \
                   -s "x = 'c'" \
                   -s "y = 'a'" \
                      "z, = choices - set(x + y)"
1000000 loops, best of 3: 0.689 usec per loop

Dies ist die ursprüngliche Lösung:

$ python -m timeit -s "x = 'c'" \
                   -s "y = 'a'" \
                      "if x == 'a' and y == 'b' or x == 'b' and y == 'a':" \
                      "    z = 'c'" \
                      "elif x == 'b' and y == 'c' or x == 'c' and y == 'b':" \
                      "    z = 'a'" \
                      "elif x == 'a' and y == 'c' or x == 'c' and y == 'a':" \
                      "    z = 'b'"
10000000 loops, best of 3: 0.310 usec per loop

Beachten Sie, dass dies die schlechteste Eingabe für die ifAnweisungen ist, da alle sechs Vergleiche ausprobiert werden müssen. Testen mit allen Werten für xund yergibt:

x = 'a', y = 'b': 0.084 usec per loop
x = 'a', y = 'c': 0.254 usec per loop
x = 'b', y = 'a': 0.133 usec per loop
x = 'b', y = 'c': 0.186 usec per loop
x = 'c', y = 'a': 0.310 usec per loop
x = 'c', y = 'b': 0.204 usec per loop

Die setbasierende Variante zeigt für verschiedene Eingänge die gleiche Leistung, ist jedoch durchweg zwischen 2 und 8 Mal langsamer . Der Grund ist, dass die if-basierte Variante viel einfacheren Code ausführt: Gleichheitstests im Vergleich zu Hashing.

Ich denke, beide Arten von Lösungen sind wertvoll: Es ist wichtig zu wissen, dass das Erstellen "komplizierter" Datenstrukturen wie Sets Sie etwas an Leistung kostet - während sie Ihnen viel Lesbarkeit und Entwicklungsgeschwindigkeit bieten . Die komplexen Datentypen sind auch viel besser, wenn sich der Code ändert: Es ist einfach, die satzbasierte Lösung auf vier, fünf, ... Variablen zu erweitern, während die if-Anweisungen schnell zu einem Wartungsalptraum werden.

Martin Geisler
quelle
1
@martinGeisler Vielen Dank für Ihre Antwort. Ich hatte keine Ahnung, dass wir solche Dinge in Python zeitlich festlegen können. Ich habe das Gefühl, dass die Chessmasters-Lösung einfach gut und effizient funktionieren würde. Ich werde versuchen, sie so zu testen, wie Sie es getan haben andere Antworten und lassen Sie es Sie wissen.
Bunny Rabbit
1
Die satzbasierte Lösung optimiert die Übersichtlichkeit und Lesbarkeit (und Eleganz ). Da aber auch die Effizienz erwähnt wurde, habe ich die Leistung der vorgeschlagenen Lösungen untersucht.
Martin Geisler
1
@ MartinGeisler: Ja, als ich das bemerkte, habe ich meinen Kommentar entfernt. Und normalerweise finde ich es zumindest interessant zu wissen, was schneller ist.
Sven Marnach
1
@BunnyRabbit: Das Timeit-Modul eignet sich hervorragend für solche Mikro-Benchmarks. Sie sollten natürlich zuerst Ihr Gesamtprogramm profilieren, um festzustellen, wo die Engpässe liegen. Wenn sie jedoch identifiziert werden, kann timeit eine gute Möglichkeit sein, schnell verschiedene Implementierungen gegeneinander auszuprobieren.
Martin Geisler
1
+1 - Benchmark-Test, der einfache Vergleichsreihen belegt, ist logisch und schnell.
Jeff Ferland
8

Versuchen Sie diese Option mithilfe von Wörterbüchern:

z = {'ab':'c', 'ba':'c', 'bc':'a', 'cb':'a', 'ac':'b', 'ca':'b'}[x+y]

Wenn der x+ySchlüssel nicht in der Karte vorhanden ist, wird natürlich ein Schlüssel erzeugt, mit KeyErrordem Sie umgehen müssen.

Wenn das Wörterbuch ein einziges Mal vorberechnet und für die zukünftige Verwendung gespeichert wird, ist der Zugriff viel schneller, da für jede Auswertung keine neuen Datenstrukturen erstellt werden müssen, sondern nur eine Zeichenfolgenverkettung und eine Wörterbuchsuche erforderlich sind:

lookup_table = {'ab':'c', 'ba':'c', 'bc':'a', 'cb':'a', 'ac':'b', 'ca':'b'}
z = lookup_table[x+y]
Óscar López
quelle
2
Nur zum Spaß, hier ist eine weitere Option: {1: 'c', 2: 'b', 3: 'a'}[ord(x)+ord(y)-ord('a')*2]Zusätzliche Komplexität ist den gesparten Speicherplatz wahrscheinlich nicht wert.
Andrew Clark
2
@ FJ: z = {1: 'a', 2: 'b', 3: 'c'}[2*('a' in x+y)+('b' in x+y)] das macht Spaß ...
ChessMaster
Ist es schneller als der OP-Originalcode? Wenn ja warum? Wie kann die Berechnung von Hashwerten schneller sein als ein einfacher Vergleich?
Max
@max es eine einzelne Hash-Berechnung, nicht eine ganze Reihe von Vergleichen und bedingten Ausdrücken
Óscar López
Cool .. Ich wusste nicht, wie schnell die Hash-Funktion ist!
Max
8
z = 'a'*('a' not in x+y) or 'b'*('b' not in x+y) or 'c'

oder weniger hackisch und unter Verwendung der bedingten Zuweisung

z = 'a' if ('a' not in x+y) else 'b' if ('b' not in x+y) else 'c'

aber wahrscheinlich ist die diktlösung schneller ... man müsste sie zeitlich festlegen.

Schachmeister
quelle
2

Ich denke, es sollte so aussehen:

z = (set(("a", "b", "c")) - set((x, y))).pop() if x != y else None
selbst benannt
quelle
12
len(set((x, y))) == 2ist die unleserlichste Art zu schreiben, die x != yich je gesehen habe :)
Sven Marnach
Ja, Sven))) Danke für deinen Kommentar. Dieses Skript hatte eine andere Grundidee, als ich anfing, es zu schreiben.)) Und schließlich habe ich vergessen, das zu bearbeiten.
Selbstbenannt am
1

Verwenden Sie das Listenverständnis, indem Sie wie andere davon ausgehen, dass einer der drei Fälle in Ihrem Code gilt:

l = ['a', 'b', 'c']
z = [n for n in l if n not in [x,y]].pop()

Oder, wie in der akzeptierten Antwort, das Tupel nutzen, um es auszupacken,

z, = [n for n in l if n not in [x,y]]
John
quelle
0

Überprüfen Sie, ob dies funktioniert

if a not in xy
    z= 'a'
if b not in xy
    z='b'
if c not in xy
    z='c'
Sundhar
quelle