Pytanie Jak mogę znaleźć brakującą wartość bardziej zwięźle?


Poniższy kod sprawdza, czy x i y są odrębnymi wartościami (zmiennymi x, y, z może mieć tylko wartości a, b, lub c) a jeśli tak, to zestawy z do trzeciej postaci:

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'

Czy można to zrobić w bardziej zwięzły, czytelny i skuteczny sposób?


76
2018-01-09 17:21


pochodzenie


Krótka odpowiedź brzmi "Tak!" Zestawy Pythona są doskonałe do sprawdzania odrębności i do obliczania nieużywanych elementów. - Raymond Hettinger
Dziękuję wszystkim za odpowiedzi, myślę, że użyję rozwiązania używając zestawu jako jego rozsądnie szybkiego i czytelnego, odpowiedź na pytania w oparciu o odnośniki Óscar López również jest interesująca. - Bunny Rabbit


Odpowiedzi:


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

Zakładam, że jeden z trzech przypadków w twoim kodzie zawiera. W takim przypadku zestaw set(("a", "b", "c")) - set((x, y)) będzie składał się z jednego elementu, który jest zwracany przez pop().

Edytować: Jak zasugerował Raymond Hettinger w komentarzach, można również użyć rozpakowania krotek, aby wyodrębnić pojedynczy element z zestawu:

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

62
2018-01-09 17:23



Jeśli używasz Pythona w wersji 2.7 / 3.1 lub nowszej, możesz napisać go jeszcze bardziej zwięźle, używając set literals, jak na przykład: z = ({'a', 'b', 'c'} - {x, y}).pop() - Taymon
The Muzyka pop() jest niepotrzebne i powolne. Zamiast tego użyj rozpakowywania krotek. Ponadto set(("a", "b", "c")) jest niezmienna, więc może być wstępnie obliczona jeden raz, pozostawiając tylko różnicę zestawu, która ma być użyta w pętli (jeśli nie jest używana w pętli, to nie zależy nam zbytnio na prędkości). - Raymond Hettinger
@Ed: Wiem, ale PO nie określił, co robić, kiedy x == y, więc pominąłem test. Łatwo dodać if x != y: Jeśli potrzebne. - Sven Marnach
Osobiście wybrałbym pierwszą, ponieważ jest bardziej oczywista niż przypadkowy przecinek. - John
Możesz chcieć wymienić set(("a", "b", "c")) przez set("abc"). - kasyc


The strip metoda to kolejna opcja, która działa szybko dla mnie:

z = 'abc'.strip(x+y) if x!=y else None

47
2018-01-09 19:15



+1 Jest również bardzo przezroczysty iw przeciwieństwie do większości odpowiedzi zajmuje się x == y. - Ed Staub
Niezły pomysł, +1; chociaż tak naprawdę to myślę "a", "b" i "c" w oryginalnym wpisie są po prostu symbolami zastępczymi dla prawdziwych wartości. To rozwiązanie nie generalizuje żadnych innych typów wartości niż łańcuchy o długości 1. - Sven Marnach
@chepner to jest twórcze! Dzięki za odpowiedź na chepner. - Bunny Rabbit


Znakomity kod Svena zrobił trochę za dużo pracy i zamiast tego użył rozpakowania krotek Muzyka pop(). Ponadto mógł dodać strażnika if x != y sprawdzić x i y być odrębnym. Oto, jak wygląda ulepszona odpowiedź:

# 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}

Oto porównanie czasowe z zestawem czasowym, aby pokazać względną wydajność:

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

Oto wyniki czasów:

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

Te czasy pokazują, że orginalna wersja Wydajność różni się dość w zależności od tego, które instrukcje if są wywoływane przez różne wartości wejściowe.


28
2018-01-09 19:06



Twój test wydaje się być stronniczy. Tak zwana "set_version" jest tylko czasami szybsza, ponieważ jest strzeżona przez dodatkową if komunikat. - ekhumoro
@ekhumoro Tak właśnie wymagała specyfikacja problemu: "sprawdza, czy x i y są odrębne wartości, a jeśli tak, to zestawy z do trzeciej postaci. "Najszybszym (i najbardziej prostym) sposobem sprawdzenia, czy wartości są różne x != y. Tylko wtedy, gdy są odrębne, ustawiamy różnicę, aby określić trzeci znak :-) - Raymond Hettinger
Chodzi mi o to, że twoje testy nie pokazują, że set_version działa lepiej ponieważ opiera się na zestawach; działa tylko lepiej dzięki ochronie if komunikat. - ekhumoro
@ekhumoro To dziwne czytanie wyników testu. Kod robi o co poprosił PO. Czasy pokazują porównywalną wydajność ze wszystkimi możliwymi grupami wejść. Od ciebie zależy, jak chcesz je interpretować. Czas dla wersji za pomocą if x != y: z, = choices - {x, y} taryf względnie dobrze w porównaniu z oryginalnym kodem OP. Nie wiem, skąd się bierze twoje nastawienie - czasy są tym, czym są, a AFAICT, to wciąż najlepsza z odpowiedzi, które zostały opublikowane. Jest zarówno czysty, jak i szybki. - Raymond Hettinger
Kilka "optymalizacji" zostało dodanych do "set-version" Svena, ale to samo nie było zrobione dla "wersji-if". Dodawanie if x != y ochrona dla "wersji-if" zapewne sprawiłaby, że byłaby bardziej spójna i wydajniejsza niż wszystkie inne rozwiązania, które zostały zaoferowane do tej pory (chociaż oczywiście nie są tak czytelne i zwięzłe). Twoja "set_version" to bardzo dobre rozwiązanie - po prostu nie całkiem tak dobre jak testy sprawiają, że się wydaje ;-) - ekhumoro


z = (set('abc') - set(x + y)).pop()

Oto wszystkie scenariusze, które pokazują, że działa:

>>> (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'

18
2018-01-09 17:24





Gdyby trzy przedmiotowe pytania nie były "a", "b" i "c", ale raczej 1, 2 i 3, możesz również użyć binarnego XOR:

z = x ^ y

Bardziej ogólnie, jeśli chcesz ustawić z do pozostałej z trzech liczb a, b i c podane dwie liczby x i y z tego zestawu możesz użyć

z = x ^ y ^ a ^ b ^ c

Oczywiście możesz wykonać wstępne obliczenia a ^ b ^ c jeśli liczby są stałe.

Tego podejścia można również używać z oryginalnymi literami:

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

Przykład:

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

Nie oczekuj, że ktoś czytający ten kod natychmiast zorientuje się, co to znaczy :)


15
2018-01-09 18:02



+1 To rozwiązanie wydaje się ładne i eleganckie; i jeśli uczynisz to swoją własną funkcją i podasz magicznej liczbie 96 nazwę, logika jest łatwa do naśladowania / utrzymania (xor_of_a_b_c = 96 # ord('a') ^ ord('b') ^ ord('c') == 96). Jednak pod względem prędkości początkowej jest to około 33% wolniej niż w długim łańcuchu if / elifs; ale 500% szybciej niż set metoda. - dr jimbob
@sven Dziękuję za wprowadzenie operatora XOR do mnie, Twoje rozwiązanie jest czyste i eleganckie, myślę, że ten przykład sprawi, że przylgnie do mojego mózgu, jeszcze raz Dziękuję :) - Bunny Rabbit


Myślę, że rozwiązanie Svena Marnacha i F.J jest piękne, ale nie jest szybsze w moim małym teście. Jest to zoptymalizowana wersja Raymonda z wykorzystaniem wstępnie obliczonej 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

To oryginalne rozwiązanie:

$ 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

Zauważ, że to jest najgorszy możliwy sygnał wejściowy dla if- ponieważ wszystkie sześć porównań będzie musiało zostać wypróbowanych. Testowanie z wszystkimi wartościami dla x i y daje:

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

The setoparty na wariancie pokazuje taką samą wydajność dla różnych wejść, ale jest konsekwentnie pomiędzy 2 i 8 razy wolniej. Powodem jest to, że ifoparty na warstwie działa znacznie prostszy kod: testy równości w porównaniu do mieszania.

Sądzę, że oba typy rozwiązań są cenne: ważne jest, aby wiedzieć, że tworzenie "skomplikowanych" struktur danych, takich jak zestawy, kosztuje Cię trochę wydajności - a jednocześnie daje dużo czytelności i szybkość rozwoju. Złożone typy danych są również znacznie lepsze, gdy zmienia się kod: łatwo jest rozszerzyć rozwiązanie oparte na zestawie na cztery, pięć, ... zmiennych, podczas gdy instrukcje if szybko zmieniają się w koszmar utrzymania.


13
2018-01-09 17:39



@martinGeisler bardzo dziękuję za twoją odpowiedź i nie miałem pojęcia, że ​​możemy zmienić takie rzeczy w pythonie. Mam przeczucie, że rozwiązanie Chessmasters będzie działało dobrze i sprawnie, spróbuję przetestować to tak, jak z inne odpowiedzi i daj znać. - Bunny Rabbit
Zoptymalizowane rozwiązanie oparte na zestawach zwięzłość i czytelność (i elegancja). Ale wydajność również wspomniano, więc poszedłem i zbadałem działanie proponowanych rozwiązań. - Martin Geisler
@MartinGeisler: Tak, kiedy to zauważyłem, usunąłem swój komentarz. I zwykle uważam, że to interesujące, przynajmniej wiedzieć, co jest szybsze. - Sven Marnach
@BunnyRabbit: moduł timeit świetnie nadaje się do takich mikro-benchmarków. Powinieneś oczywiście najpierw zapisz swój ogólny program aby określić, gdzie występują wąskie gardła, ale gdy zostaną zidentyfikowane, timeit może być świetnym sposobem na szybkie wypróbowanie różnych implementacji względem siebie. - Martin Geisler
+1 - test porównawczy, który sprawdza proste serie porównywanych pozycji, jest logiczny i szybki. - Jeff Ferland


Wypróbuj tę opcję, używając słowników:

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

Oczywiście, jeśli x+y klucz nie jest obecny na mapie, spowoduje powstanie KeyError którym będziesz musiał sobie poradzić.

Jeśli słownik zostanie wstępnie skompresowany jednorazowo i zapisany do wykorzystania w przyszłości, dostęp będzie znacznie szybszy, ponieważ nie będzie konieczne tworzenie nowych struktur danych dla każdej oceny, potrzebne jest tylko połączenie ciągów i wyszukiwanie słownika:

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

8
2018-01-09 17:45



Dla zabawy jest jeszcze jedna opcja: {1: 'c', 2: 'b', 3: 'a'}[ord(x)+ord(y)-ord('a')*2]dodatkowa złożoność prawdopodobnie nie jest jednak warta zaoszczędzonej przestrzeni. - Andrew Clark
@ F.J: z = {1: 'a', 2: 'b', 3: 'c'}[2*('a' in x+y)+('b' in x+y)]  to jest fajne... - ChessMaster
+1 za bycie kreatywnym i za posiadanie szybkiego kodu. - Raymond Hettinger
Wohoo to szybko! - Bunny Rabbit
Czy jest szybszy niż oryginalny kod OP? jeśli tak, dlaczego? W jaki sposób obliczanie wartości skrótu może być szybsze niż proste porównanie? - max


z = 'a'*('a' not in x+y) or 'b'*('b' not in x+y) or 'c'

lub mniej hackish i przy użyciu warunkowego przypisania

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

ale prawdopodobnie rozwiązanie dyktatorskie jest szybsze ... musielibyśmy to zrobić.


8
2018-01-09 17:44