Der folgende Code überprüft , ob x
und y
sind verschiedene Werte (die Variablen x
, y
, z
können nur Werte a
, b
oder c
) und wenn ja, setzt z
auf 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?
python
benchmarking
microbenchmark
Häschen
quelle
quelle
Antworten:
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 wirdpop()
.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))
quelle
z = ({'a', 'b', 'c'} - {x, y}).pop()
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).x == y
, deshalb habe ich den Test weggelassen. Es ist einfach genug,if x != y:
bei Bedarf hinzuzufügen .set(("a", "b", "c"))
durchset("abc")
.Die
strip
Methode ist eine weitere Option, die für mich schnell ausgeführt wird:z = 'abc'.strip(x+y) if x!=y else None
quelle
"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.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 != y
zu ü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.
quelle
if
Anweisung geschützt wird .x != y
. Nur wenn sie verschieden sind, bestimmen wir den Satzunterschied, um das dritte Zeichen zu bestimmen :-)set_version
besser ist, weil sie auf Mengen basiert . es funktioniert nur aufgrund der Schutzanweisung besserif
.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.if x != y
Schutzes 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 ;-)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'
quelle
Wenn die drei Elemente in Frage nicht waren
"a"
,"b"
und"c"
, sondern1
,2
und3
könnten Sie auch eine binäre XOR verwenden:Wenn Sie ganz allgemein festlegen möchten
z
auf die verbleibende eine von drei Zahlena
,b
undc
zwei Zahlen angegebenx
undy
aus diesem Set können Sie verwendenNatürlich können Sie vorberechnen,
a ^ b ^ c
wenn 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 :)
quelle
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 vonif / elif
s; aber 500% schneller als dieset
Methode.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
if
Anweisungen ist, da alle sechs Vergleiche ausprobiert werden müssen. Testen mit allen Werten fürx
undy
ergibt: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
set
basierende Variante zeigt für verschiedene Eingänge die gleiche Leistung, ist jedoch durchweg zwischen 2 und 8 Mal langsamer . Der Grund ist, dass dieif
-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.
quelle
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+y
Schlüssel nicht in der Karte vorhanden ist, wird natürlich ein Schlüssel erzeugt, mitKeyError
dem 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]
quelle
{1: 'c', 2: 'b', 3: 'a'}[ord(x)+ord(y)-ord('a')*2]
Zusätzliche Komplexität ist den gesparten Speicherplatz wahrscheinlich nicht wert.z = {1: 'a', 2: 'b', 3: 'c'}[2*('a' in x+y)+('b' in x+y)]
das macht Spaß ...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.
quelle
Ich denke, es sollte so aussehen:
z = (set(("a", "b", "c")) - set((x, y))).pop() if x != y else None
quelle
len(set((x, y))) == 2
ist die unleserlichste Art zu schreiben, diex != y
ich je gesehen habe :)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]]
quelle
Ü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'
quelle