Składnia Pythona dla „jeśli a lub b lub c, ale nie wszystkie”

133

Mam skrypt w języku Python, który może otrzymać zero lub trzy argumenty wiersza poleceń. (Albo działa z zachowaniem domyślnym, albo wymaga określenia wszystkich trzech wartości).

Jaka jest idealna składnia dla czegoś takiego:

if a and (not b or not c) or b and (not a or not c) or c and (not b or not a):

?

Chris Wilson
źródło
4
Może zacząć od czegoś takiego jak `jeśli len (sys.argv) == 0:
Edgar Aroutiounian
6
@EdgarAroutiounian len(sys.argv)zawsze będzie wynosić co najmniej 1: zawiera plik wykonywalny jako argv[0].
RoadieRich
10
Treść pytania nie pasuje do tytułu pytania. Czy chcesz sprawdzić „czy a lub b lub c, ale nie wszystkie” lub „czy dokładnie jedno z a, b i c” (jak w podanym wyrażeniu)?
Doug McClean
2
Co możesz powiedzieć o a + b + c?
gukoff
6
Czekaj, pytanie, może przyjąć zero lub trzy argumenty. nie mógłbyś po prostu powiedzieć if not (a and b and c)(zero argumentów), a następnie if a and b and c(wszystkie trzy argumenty)?
akolita

Odpowiedzi:

238

Jeśli masz na myśli minimalną formę, idź z tym:

if (not a or not b or not c) and (a or b or c):

Co tłumaczy tytuł twojego pytania.

AKTUALIZACJA: jak słusznie powiedzieli Volatility i Supr, możesz zastosować prawo De Morgana i uzyskać równoważne:

if (a or b or c) and not (a and b and c):

Moja rada jest taka, aby używać dowolnej formy, która jest ważniejsza dla Ciebie i dla innych programistów. Pierwsza oznacza „jest coś fałszywego, ale też coś prawdziwego” , druga „jest coś prawdziwego, ale nie wszystko” . Gdybym miał zoptymalizować lub zrobić to na sprzęcie, wybrałbym drugą, tutaj po prostu wybrałbym najbardziej czytelny (również biorąc pod uwagę warunki, które będziesz testować i ich nazwy). Ja wybrałem pierwszy.

Stefano Sanfilippo
źródło
3
Wszystkie świetne odpowiedzi, ale to wygrywa ze względu na zwięzłość, z dużym zwarciem. Dziękuje wszystkim!
Chris Wilson
38
Zrobiłbym to jeszcze bardziej zwięźle i if not (a and b and c) and (a or b or c)
wybrałbym
208
Albo wręcz if (a or b or c) and not (a and b and c)idealnie pasować do tytułu;)
Supr
3
@HennyH Wydaje mi się, że pytanie to „co najmniej jeden warunek jest prawdziwy”, a nie „tylko jeden warunek jest prawdziwy”.
Zmienność
63
@Suprif any([a,b,c]) and not all([a,b,c])
eternalmatt
240

Co powiesz na:

conditions = [a, b, c]
if any(conditions) and not all(conditions):
   ...

Inny wariant:

if 1 <= sum(map(bool, conditions)) <= 2:
   ...
defuz
źródło
2
sum(conditions)może się nie udać, jeśli 2na przykład którykolwiek z nich powróci , czyli True.
eumiro
7
To prawda, że ​​potrzebujesz brzydkiegosum(map(bool, conditions))
jamylaka
5
Zauważ, że nie powoduje to zwarcia, ponieważ wszystkie warunki są wstępnie oceniane.
georg
14
@PaulScheltema Pierwsza forma jest bardziej zrozumiała dla każdego.
cmh
6
To „każdy i nie wszystko” jest najlepszą i najjaśniejszą z metod logicznych, po prostu pamiętaj o ważnej różnicy między obecnością argumentu a argumentem „prawdziwym”
wim
115

To pytanie miało już wiele bardzo pozytywnych odpowiedzi i akceptowaną odpowiedź, ale jak dotąd wszystkie z nich były rozpraszane różnymi sposobami wyrażenia problemu logicznego i pominęły kluczowy punkt:

Mam skrypt w języku Python, który może otrzymać zero lub trzy argumenty wiersza poleceń. (Albo działa z zachowaniem domyślnym, albo wymaga określenia wszystkich trzech wartości)

Ta logika nie powinna leżeć w gestii twojego kodu , raczej powinna być obsługiwana przezargparsemoduł. Nie kłopocz się pisaniem złożonej instrukcji if, zamiast tego wolisz ustawić parser argumentów w następujący sposób:

#!/usr/bin/env python
import argparse as ap
parser = ap.ArgumentParser()
parser.add_argument('--foo', nargs=3, default=['x', 'y', 'z'])
args = parser.parse_args()
print(args.foo)

I tak, powinna to być opcja, a nie argument pozycyjny, bo przecież jest opcjonalna .


edytowane: Aby rozwiązać problem LarsHa w komentarzach, poniżej znajduje się przykład, jak można by to napisać, gdybyś był pewien, że chciałbyś mieć interfejs z 3 lub 0argumentami pozycyjnymi . Jestem zdania, że ​​poprzedni interfejs ma lepszy styl, ponieważ opcjonalnymi argumentami powinny być opcje , ale tutaj jest alternatywne podejście ze względu na kompletność. Zwróć uwagę na nadpisywanie kwargusagepodczas tworzenia parsera, ponieważ wargparseprzeciwnym razie automatycznie wygeneruje mylący komunikat o użytkowaniu!

#!/usr/bin/env python
import argparse as ap
parser = ap.ArgumentParser(usage='%(prog)s [-h] [a b c]\n')
parser.add_argument('abc', nargs='*', help='specify 3 or 0 items', default=['x', 'y', 'z'])
args = parser.parse_args()
if len(args.abc) != 3:
  parser.error('expected 3 arguments')
print(args.abc)

Oto kilka przykładów użycia:

# default case
wim@wim-zenbook:/tmp$ ./three_or_none.py 
['x', 'y', 'z']

# explicit case
wim@wim-zenbook:/tmp$ ./three_or_none.py 1 2 3
['1', '2', '3']

# example failure mode
wim@wim-zenbook:/tmp$ ./three_or_none.py 1 2 
usage: three_or_none.py [-h] [a b c]
three_or_none.py: error: expected 3 arguments
wim
źródło
4
Tak, dodałem to celowo. Byłoby możliwe ustawienie argumentu w pozycji pozycyjnej i wymuszenie, że dokładnie 3 lub 0 są zużywane, ale nie byłoby to dobrym CLI, więc nie polecam tego.
wim
8
Osobny problem. Nie wierzysz, że to dobry CLI i możesz argumentować o tym, a OP może zostać przekonany. Ale twoja odpowiedź na tyle odbiega od pytania, że ​​trzeba wspomnieć o zmianie specyfikacji. Wygląda na to, że dostosowujesz specyfikację do dostępnego narzędzia, nie wspominając o zmianie.
LarsH
2
@LarsH OK, dodałem przykład, który lepiej pasuje do oryginalnego interfejsu zawartego w pytaniu. Teraz wygina narzędzie, aby spełniało dostępne specyfikacje ...;)
wim
2
To jedyna odpowiedź, za którą głosowałem. +1 za odpowiedź na prawdziwe pytanie .
Jonathon Reinhart
1
+1. Forma CLI jest ważną kwestią styczną, nie całkowicie odrębną, jak powiedziała inna osoba. Głosowałem za Twoim postem, podobnie jak inne - twój jest źródłem problemu i oferuje eleganckie rozwiązanie, podczas gdy inne posty odpowiadają na dosłowne pytanie. Oba rodzaje odpowiedzi są przydatne i zasługują na +1.
Ben Lee
31

Poszedłbym na:

conds = iter([a, b, c])
if any(conds) and not any(conds):
    # okay...

Myślę, że to powinno dość skutecznie powodować zwarcie

Wyjaśnienie

Tworząc condsiterator, pierwsze użycie anywill powoduje zwarcie i pozostawienie iteratora wskazującego na następny element, jeśli którykolwiek element jest prawdziwy; w przeciwnym razie pochłonie całą listę i będzie False. Następna anybierze pozostałe elementy w iterowalnej i upewnia się, że nie ma innych prawdziwych wartości ... Jeśli tak, całe stwierdzenie nie może być prawdziwe, więc nie ma jednego unikalnego elementu (więc krótkie spięcia jeszcze raz). Ostatni anyalbo powróci, Falsealbo wyczerpie iterowalne i będzie True.

uwaga: powyższe sprawdza, czy jest ustawiony tylko jeden warunek


Jeśli chcesz sprawdzić, czy co najmniej jeden element jest ustawiony, ale nie każdy element jest ustawiony, możesz użyć:

not all(conds) and any(conds)
Jon Clements
źródło
6
Nie rozumiem. Brzmi tak: jeśli prawda i nieprawda. Pomóż mi zrozumieć.
rGil
1
@rGil: brzmi jak „jeśli niektóre jabłka są czerwone, a inne nie” - to to samo, co „niektóre jabłka są czerwone, ale nie wszystkie”.
georg
2
Nawet z wyjaśnieniem nie rozumiem zachowania ... W [a, b, c] = [True, True, False]przypadku False, gdy kod nie powinien się "drukować" , podczas gdy oczekiwany wynik jest True?
awesoon
6
To całkiem sprytne, ALE: zastosowałbym to podejście, gdybyś nie wiedział, ile warunków masz z góry, ale w przypadku ustalonej, znanej listy warunków, utrata czytelności po prostu nie jest tego warta.
puszysty
4
To nie powoduje zwarcia. Lista jest kompletnie skonstruowana przed przekazaniem do iter. anyi allleniwie pochłonie listę, to prawda, ale lista była już w pełni oceniona, zanim tam dotrzesz!
icktoofay
22

Zdanie angielskie:

„Jeśli a lub b lub c, ale nie wszystkie z nich”

Przekłada się na tę logikę:

(a or b or c) and not (a and b and c)

Słowo „ale” zwykle implikuje koniunkcję, innymi słowy „i”. Co więcej, „wszystkie z nich” przekłada się na połączenie warunków: ten warunek i ten warunek oraz inny warunek. „Nie” odwraca całą koniunkcję.

Nie zgadzam się z zaakceptowaną odpowiedzią. Autor zaniedbał zastosowanie najprostszej interpretacji specyfikacji i zaniedbał zastosowanie prawa De Morgana, aby uprościć wyrażenie dla mniejszej liczby operatorów:

 not a or not b or not c  ->  not (a and b and c)

twierdząc, że odpowiedź jest „minimalną formą”.

Kaz
źródło
Właściwie ta forma jest minimalna. Jest to minimalna forma PoS dla wyrażenia.
Stefano Sanfilippo
10

A co z: (wyjątkowy stan)

if (bool(a) + bool(b) + bool(c) == 1):

Zauważ, że jeśli dopuszczasz dwa warunki, możesz to zrobić

if (bool(a) + bool(b) + bool(c) in [1,2]):
Casimir et Hippolyte
źródło
1
Gwoli ścisłości, pytanie dotyczy dwóch warunków. Co najmniej jeden, ale nie wszyscy = 1 ze wszystkich lub 2 ze wszystkich
Marius Balčytis
IMHO, powinieneś przeliterować drugi jako 1 <= bool(a) + bool(b) + bool(c) <= 2.
Przywróć Monikę
9

To zwraca, Truejeśli spełniony jest jeden i tylko jeden z trzech warunków True. Prawdopodobnie to, czego chciałeś w swoim przykładowym kodzie.

if sum(1 for x in (a,b,c) if x) == 1:
eumiro
źródło
Nie tak ładne, jak odpowiedź @defuz
jamylak
To sprawdza, czy dokładnie jeden z warunków jest prawdziwy. Zwraca False, jeśli dwa warunki są spełnione, podczas gdy „a lub b lub c, ale nie wszystkie z nich”, powinno zwracać True, jeśli dwa z warunków są spełnione.
Boris
6

Żeby było jasne, chcesz podjąć decyzję na podstawie tego, ile parametrów jest logicznych PRAWDA (w przypadku argumentów łańcuchowych - niepustych)?

argsne = (1 if a else 0) + (1 if b else 0) + (1 if c else 0)

Wtedy podjąłeś decyzję:

if ( 0 < argsne < 3 ):
 doSth() 

Teraz logika jest bardziej przejrzysta.

Żeglarz Naddunajski
źródło
5

A dlaczego ich nie policzyć?

import sys
a = sys.argv
if len(a) = 1 :  
    # No arguments were given, the program name count as one
elif len(a) = 4 :
    # Three arguments were given
else :
    # another amount of arguments was given
Louis
źródło
5

Jeśli nie masz nic przeciwko temu, że jesteś trochę tajemniczy, możesz po prostu rzucić, z 0 < (a + b + c) < 3którym zwróci, truejeśli masz od jednego do dwóch prawdziwych stwierdzeń i fałszywe, jeśli wszystkie są fałszywe lub żadne nie jest fałszywe.

Upraszcza to również, jeśli używasz funkcji do oceny wartości bools, ponieważ obliczasz zmienne tylko raz, co oznacza, że ​​możesz pisać funkcje w tekście i nie musisz tymczasowo przechowywać zmiennych. (Przykład:. 0 < ( a(x) + b(x) + c(x) ) < 3)

Spinno
źródło
4

Pytanie mówi, że potrzebujesz albo wszystkich trzech argumentów (a i b i c), albo żadnego z nich (nie (a lub b lub c))

To daje:

(a i b i c) lub nie (a lub b lub c)

Relaks na Cyprze
źródło
4

Jak rozumiem, masz funkcję, która otrzymuje 3 argumenty, ale jeśli nie, będzie działać z zachowaniem domyślnym. Ponieważ nie wyjaśniłeś, co powinno się stać, gdy podano 1 lub 2 argumenty, zakładam, że powinno to po prostu działać w sposób domyślny. W takim przypadku myślę, że następująca odpowiedź będzie bardzo korzystna:

def method(a=None, b=None, c=None):
    if all([a, b, c]):
        # received 3 arguments
    else:
        # default behavior

Jeśli jednak chcesz, aby 1 lub 2 argumenty były obsługiwane inaczej:

def method(a=None, b=None, c=None):
    args = [a, b, c]
    if all(args):
        # received 3 arguments
    elif not any(args):
        # default behavior
    else:
        # some args (raise exception?)

uwaga: przy założeniu, że Falsewartości „ ” nie będą przekazywane do tej metody.

Inbar Rose
źródło
sprawdzenie prawdziwości argumentu to inna sprawa niż sprawdzenie, czy argument jest obecny, czy nie
wim
@wim Więc konwertuje pytanie tak, aby pasowało do Twojej odpowiedzi. Moduł argparse nie ma nic wspólnego z pytaniem, dodaje kolejny import, a jeśli OP nie planuje w ogóle używać argparse, nie pomoże im to w ogóle. Ponadto, jeśli „skrypt” nie jest samodzielny, ale jest modułem lub funkcją w większym zestawie kodu, może już mieć parser argumentów, a ta konkretna funkcja w tym większym skrypcie może być domyślna lub dostosowana. Ze względu na ograniczone informacje z PO nie mogę wiedzieć, jak ta metoda powinna działać, ale można bezpiecznie założyć, że PO nie przechodzi wartości bools.
Inbar Rose
Pytanie wyraźnie brzmiało: „Mam skrypt Pythona, który może otrzymać zero lub trzy argumenty wiersza poleceń”, ale nie mówiło „Mam funkcję, która otrzymuje 3 argumenty”. Ponieważ moduł argparse jest preferowanym sposobem obsługi argumentów wiersza poleceń w Pythonie, automatycznie ma wszystko związane z pytaniem. Wreszcie, Python jest „w zestawie z bateriami” - nie ma żadnych wad „dodawania kolejnego importu”, gdy ten moduł jest częścią standardowych bibliotek.
wim
@wim Pytanie jest dość niejasne (na przykład treść nie pasuje do tytułu). Myślę, że pytanie jest na tyle niejasne, że jest to ważna odpowiedź na jakąś jego interpretację.
Przywróć Monikę
2

Jeśli pracujesz z iteratorem warunków, dostęp do niego może być wolny. Ale nie musisz uzyskiwać dostępu do każdego elementu więcej niż jeden raz i nie zawsze musisz czytać całość. Oto rozwiązanie, które będzie działać z nieskończonymi generatorami:

#!/usr/bin/env python3
from random import randint
from itertools import tee

def generate_random():
    while True:
        yield bool(randint(0,1))

def any_but_not_all2(s): # elegant
    t1, t2 = tee(s)
    return False in t1 and True in t2 # could also use "not all(...) and any(...)"

def any_but_not_all(s): # simple
    hadFalse = False
    hadTrue = False
    for i in s:
        if i:
            hadTrue = True
        else:
            hadFalse = True
        if hadTrue and hadFalse:
            return True
    return False


r1, r2 = tee(generate_random())
assert any_but_not_all(r1)
assert any_but_not_all2(r2)

assert not any_but_not_all([True, True])
assert not any_but_not_all2([True, True])

assert not any_but_not_all([])
assert not any_but_not_all2([])

assert any_but_not_all([True, False])
assert any_but_not_all2([True, False])
Janus Troelsen
źródło
0

Kiedy każdy dany booljest Truelub kiedy każdy dany booljest False
wszyscy są sobie równi!

Więc, po prostu trzeba znaleźć dwa elementy rozpoznawaną różnych bools
wiedzieć, że istnieje co najmniej jeden True, a co najmniej jeden False.

Moje krótkie rozwiązanie:

not bool(a)==bool(b)==bool(c)

Wierzę, że to zwarcia, bo AFAIK a==b==crówna się a==b and b==c.

Moje uogólnione rozwiązanie:

def _any_but_not_all(first, iterable): #doing dirty work
    bool_first=bool(first)
    for x in iterable:
        if bool(x) is not bool_first:
            return True
    return False

def any_but_not_all(arg, *args): #takes any amount of args convertable to bool
    return _any_but_not_all(arg, args)

def v_any_but_not_all(iterable): #takes iterable or iterator
    iterator=iter(iterable)
    return _any_but_not_all(next(iterator), iterator)

Napisałem też kod zajmujący się wieloma iteracjami, ale usunąłem go stąd, ponieważ uważam, że to bezcelowe. Jest jednak nadal dostępny tutaj .

GingerPlusPlus
źródło
-2

Jest to w zasadzie funkcja „niektóre (ale nie wszystkie)” (w przeciwieństwie do funkcji any()i all()wbudowanych).

Oznacza to, że wśród wyników powinny znajdować się Falses i True s. Dlatego możesz wykonać następujące czynności:

some = lambda ii: frozenset(bool(i) for i in ii).issuperset((True, False))

# one way to test this is...
test = lambda iterable: (any(iterable) and (not all(iterable))) # see also http://stackoverflow.com/a/16522290/541412

# Some test cases...
assert(some(()) == False)       # all() is true, and any() is false
assert(some((False,)) == False) # any() is false
assert(some((True,)) == False)  # any() and all() are true

assert(some((False,False)) == False)
assert(some((True,True)) == False)
assert(some((True,False)) == True)
assert(some((False,True)) == True)

Jedną z zalet tego kodu jest to, że wystarczy raz przejść przez wynikowe elementy (wartości logiczne).

Jedną z wad jest to, że wszystkie te wyrażenia prawdy są zawsze oceniane i nie powodują zwarć, takich jak operatory or/ and.

Abbafei
źródło
1
Myślę, że to niepotrzebna komplikacja. Dlaczego zestaw zamrożony zamiast zwykłego starego zestawu? Dlaczego, .issupersetzamiast po prostu sprawdzić długość 2, booli tak nie może zwrócić niczego innego niż True i False. Po co przypisywać lambdę (czytaj: funkcję anonimową) do nazwy zamiast po prostu używać def?
wim
1
składnia lambda jest dla niektórych bardziej logiczna. i tak są tej samej długości, ponieważ potrzebujesz, returnjeśli używasz def. myślę, że ogólność tego rozwiązania jest fajna. nie jest konieczne ograniczanie się do wartości logicznych, zasadniczo pytanie brzmi: „jak upewnić się, że wszystkie te elementy występują na mojej liście”. dlaczego, setjeśli nie potrzebujesz zmienności? Większa niezmienność jest zawsze lepsza, jeśli nie potrzebujesz wydajności.
Janus Troelsen
@JanusTroelsen Masz rację! Oto kilka powodów, dla których zrobiłem to w ten sposób; sprawia, że ​​jest to dla mnie łatwiejsze i wyraźniejsze. Zwykle dostosowuję Pythona do mojego sposobu kodowania :-).
Abbafei
ale to nie zadziała na nieskończonych generatorach: P zobacz moją odpowiedź :) podpowiedź:tee
Janus Troelsen
@JanusTroelsen Zdaję sobie z tego sprawę :-). Właściwie to miałem na początku odwrotnie (z True / False w zestawie i iterowalnym w parametrze metody), ale zdałem sobie sprawę, że to nie zadziała z nieskończonymi generatorami, a użytkownik może nie zdawać sobie sprawy (ponieważ ten fakt nie jest (jeszcze) wspomniane w dokumentacji dla iterowalnych parametrów metody set) i przynajmniej w tym przypadku jest oczywiste, że nie będzie wymagało nieskończonych iteratorów. Byłem świadomy, itertools.teeale 1) szukałem
jednowierszowego dokumentu, który byłby