Sintaksis python untuk "jika a atau b atau c tetapi tidak semuanya"

130

Saya memiliki skrip python yang dapat menerima argumen baris perintah nol atau tiga. (Entah itu berjalan pada perilaku default atau membutuhkan ketiga nilai yang ditentukan.)

Apa sintaks yang ideal untuk sesuatu seperti:

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
sumber
4
mungkin memulai dengan sesuatu seperti `if len (sys.argv) == 0:
Edgar Aroutiounian
6
@EdgarAroutiounian len(sys.argv)akan selalu setidaknya 1: ini termasuk executable as argv[0].
RoadieRich
10
Isi pertanyaan tidak sesuai dengan judul pertanyaan. Apakah Anda ingin memeriksa "jika a atau b atau c tetapi tidak semuanya" atau "jika tepat salah satu dari a, b, dan c" (seperti ungkapan yang Anda berikan)?
Doug McClean
2
Apa yang bisa Anda katakan tentang + b + c?
gukoff
6
Tunggu, pertanyaan, butuh nol atau tiga argumen. tidak bisakah Anda mengatakan if not (a and b and c)(nol arg), dan kemudian if a and b and c(ketiga arg)
acolyte

Jawaban:

236

Jika Anda bermaksud bentuk minimal, ikuti ini:

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

Yang menerjemahkan judul pertanyaan Anda.

UPDATE: seperti yang dikatakan dengan benar oleh Volatility dan Supr, Anda dapat menerapkan hukum De Morgan dan mendapatkan yang setara:

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

Saran saya adalah menggunakan formulir apa pun yang lebih penting bagi Anda dan pemrogram lainnya. Yang pertama berarti "ada sesuatu yang salah, tetapi juga sesuatu yang benar" , yang kedua "Ada sesuatu yang benar, tetapi tidak semuanya" . Jika saya mengoptimalkan atau melakukan ini dalam perangkat keras, saya akan memilih yang kedua, di sini pilih saja yang paling mudah dibaca (juga dengan mempertimbangkan kondisi yang akan Anda uji dan nama mereka). Saya memilih yang pertama.

Stefano Sanfilippo
sumber
3
Semua jawaban bagus, tetapi ini menang untuk keringkasan, dengan hubungan arus pendek yang bagus. Terima kasih semuanya!
Chris Wilson
38
Saya akan membuatnya lebih ringkas dan cocok denganif not (a and b and c) and (a or b or c)
Volatilitas
208
Atau bahkan if (a or b or c) and not (a and b and c)untuk menyamakan gelar dengan sempurna;)
Supr
3
@ Henny Saya yakin pertanyaannya menanyakan "setidaknya satu kondisi benar tetapi tidak semua", tidak "hanya satu syarat benar".
Volatilitas
63
@Suprif any([a,b,c]) and not all([a,b,c])
eternalmatt
238

Bagaimana tentang:

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

Varian lainnya:

if 1 <= sum(map(bool, conditions)) <= 2:
   ...
defuz
sumber
2
sum(conditions)dapat salah jika salah satu dari mereka kembali 2misalnya, yaitu True.
eumiro
7
Benar Anda akan membutuhkan jeleksum(map(bool, conditions))
jamylak
5
Perhatikan bahwa ini bukan hubungan arus pendek, karena semua kondisi sudah dievaluasi sebelumnya.
georg
14
@ PaulScheltema Bentuk pertama lebih mudah dimengerti oleh siapa pun.
cmh
6
Ini "siapa saja dan tidak semua" adalah yang terbaik dan paling jelas dari metode boolean, hanya berhati-hati dengan perbedaan penting antara argumen yang hadir dan argumen yang 'benar'
wim
115

Pertanyaan ini sudah memiliki banyak jawaban yang sangat tinggi dan jawaban yang diterima, tetapi semuanya sejauh ini terganggu oleh berbagai cara untuk mengekspresikan masalah boolean dan melewatkan satu poin penting:

Saya memiliki skrip python yang dapat menerima argumen baris perintah nol atau tiga. (Entah itu berjalan pada perilaku default atau membutuhkan ketiga nilai yang ditentukan)

Logika ini seharusnya tidak menjadi tanggung jawab kode Anda , tetapi harus ditangani olehargparse modul. Jangan repot-repot menulis pernyataan if kompleks, alih-alih lebih suka mengatur parser argumen Anda seperti ini:

#!/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)

Dan ya, itu harus menjadi pilihan bukan argumen posisi, karena bagaimanapun juga opsional .


diedit: Untuk mengatasi kekhawatiran LarsH dalam komentar, di bawah ini adalah contoh bagaimana Anda bisa menulisnya jika Anda yakin Anda menginginkan antarmuka dengan 3 atau 0argumen posisi . Saya berpendapat bahwa antarmuka sebelumnya adalah gaya yang lebih baik, karenaargumen opsional harus menjadi pilihan , tapi inilah pendekatan alternatif demi kelengkapan. Catat kwarg utamausagesaat membuat parser Anda, karenaargparseakan secara otomatis menghasilkan pesan penggunaan yang menyesatkan!

#!/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)

Berikut ini beberapa contoh penggunaan:

# 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
sumber
4
Ya, saya menambahkannya dengan sengaja. Mungkin untuk membuat argumen posisional, dan menegakkan bahwa tepat 3 atau 0 dikonsumsi, tetapi itu tidak akan membuat CLI yang baik jadi saya belum merekomendasikannya.
wim
8
Pisahkan masalah. Anda tidak percaya itu CLI yang baik, dan Anda bisa berdebat untuk hal itu, dan OP dapat dibujuk. Tetapi jawaban Anda menyimpang dari pertanyaan cukup signifikan sehingga perubahan spesifikasi perlu disebutkan. Anda tampaknya menekuk spec agar sesuai dengan alat yang tersedia, tanpa menyebutkan perubahannya.
LarsH
2
@ LarsH OK, saya telah menambahkan contoh yang lebih cocok dengan antarmuka asli yang tersirat dalam pertanyaan. Sekarang alat tersebut sedang ditekuk untuk memenuhi spesifikasi yang tersedia ...;)
wim
2
Ini adalah satu-satunya jawaban yang saya undur. +1 untuk menjawab pertanyaan sesungguhnya .
Jonathon Reinhart
1
+1. Bentuk CLI adalah masalah tangensial yang penting, tidak sepenuhnya terpisah seperti kata orang lain. Saya memutakhirkan posting Anda dan juga yang lainnya - posting Anda menjadi sumber masalah dan menawarkan solusi yang elegan, sedangkan posting lain menjawab pertanyaan literal. Dan kedua jenis jawaban itu bermanfaat dan layak diberi +1.
Ben Lee
32

Saya akan pergi untuk:

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

Saya pikir ini harus korsleting yang cukup efisien

Penjelasan

Dengan membuat condsiterator, penggunaan pertama anyakan hubungan pendek dan biarkan iterator menunjuk ke elemen berikutnya jika ada item yang benar; jika tidak, ia akan mengkonsumsi seluruh daftar dan menjadi False. Berikutnya anymengambil item yang tersisa di iterable, dan memastikan daripada tidak ada nilai true lainnya ... Jika ada, seluruh pernyataan tidak mungkin benar, sehingga tidak ada satu elemen unik (sirkuit pendek begitu lagi). Yang terakhir anyakan kembali Falseatau akan menghabiskan iterable dan menjadi True.

catatan: pemeriksaan di atas memeriksa apakah hanya satu syarat yang ditetapkan


Jika Anda ingin memeriksa apakah satu atau lebih item, tetapi tidak setiap item ditetapkan, maka Anda dapat menggunakan:

not all(conds) and any(conds)
Jon Clements
sumber
5
Saya tidak mengerti. Bunyinya seperti: jika Benar dan tidak Benar. Bantu aku mengerti.
rGil
1
@ rGil: berbunyi seperti "jika beberapa apel berwarna merah, dan beberapa tidak" - itu sama dengan mengatakan "beberapa apel berwarna merah, tetapi tidak semuanya".
georg
2
Bahkan dengan penjelasan saya tidak dapat memahami perilaku ... Dengan [a, b, c] = [True, True, False]tidak seharusnya kode Anda "dicetak" False, sementara output yang diharapkan True?
awesoon
6
Ini cukup pintar, TETAPI: Saya akan menggunakan pendekatan ini jika Anda tidak tahu berapa banyak kondisi yang Anda hadapi di muka, tetapi untuk daftar kondisional yang sudah diketahui, hilangnya keterbacaan sama sekali tidak sepadan.
lembut
4
Ini bukan hubungan arus pendek. Daftar ini sepenuhnya dibangun sebelum dilewatkan iter. anydan allmalas akan mengkonsumsi daftar, benar, tetapi daftar itu sudah sepenuhnya dievaluasi pada saat Anda sampai di sana!
icktoofay
22

Kalimat bahasa Inggris:

“Jika a atau b atau c tetapi tidak semuanya”

Diterjemahkan ke logika ini:

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

Kata "tetapi" biasanya menyiratkan konjungsi, dengan kata lain "dan". Lebih jauh, "mereka semua" diterjemahkan menjadi gabungan dari kondisi: kondisi ini, dan kondisi itu, dan kondisi lainnya. The "not" membalikkan seluruh konjungsi itu.

Saya tidak setuju dengan jawaban yang diterima. Penulis lalai menerapkan interpretasi paling langsung ke spesifikasi, dan lalai menerapkan Hukum De Morgan untuk menyederhanakan ekspresi ke lebih sedikit operator:

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

sambil mengklaim bahwa jawabannya adalah "bentuk minimal".

Kaz
sumber
Sebenarnya, bentuk itu minimal. Ini bentuk PoS minimal untuk ekspresi.
Stefano Sanfilippo
10

Ini kembali Truejika satu dan hanya satu dari tiga kondisi True. Mungkin yang Anda inginkan dalam kode contoh Anda.

if sum(1 for x in (a,b,c) if x) == 1:
eumiro
sumber
Tidak secantik jawaban oleh @defuz
jamylak
10

Bagaimana dengan: (kondisi unik)

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

Perhatikan, jika Anda mengizinkan dua syarat juga, Anda bisa melakukannya

if (bool(a) + bool(b) + bool(c) in [1,2]):
Casimir et Hippolyte
sumber
1
Sebagai catatan, pertanyaan menanyakan dua syarat. Setidaknya satu, tetapi tidak semuanya = 1 dari semua atau 2 dari semua
Marius Balčytis
IMHO Anda harus mengeja yang kedua sebagai 1 <= bool(a) + bool(b) + bool(c) <= 2.
Pasang kembali Monica
6

Agar lebih jelas, Anda ingin membuat keputusan berdasarkan berapa banyak parameter yang BENAR (dalam kasus argumen string - bukan kosong)?

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

Lalu Anda membuat keputusan:

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

Sekarang logikanya lebih jelas.

Pelaut Danubia
sumber
5

Dan mengapa tidak menghitungnya saja?

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
sumber
5

Jika Anda tidak keberatan menjadi sedikit samar Anda bisa simly roll dengan 0 < (a + b + c) < 3yang akan kembali truejika Anda memiliki antara satu dan dua pernyataan benar dan salah jika semuanya salah atau tidak ada yang salah.

Ini juga menyederhanakan jika Anda menggunakan fungsi untuk mengevaluasi bools karena Anda hanya mengevaluasi variabel satu kali dan yang berarti Anda dapat menulis fungsi inline dan tidak perlu menyimpan sementara variabel. (Contoh:. 0 < ( a(x) + b(x) + c(x) ) < 3)

Spinno
sumber
4

Pertanyaannya menyatakan bahwa Anda memerlukan ketiga argumen (a dan b dan c) atau tidak satupun (tidak (a atau b atau c))

Ini memberi:

(a dan b dan c) atau tidak (a atau b atau c)

Bersantai Di Siprus
sumber
4

Seperti yang saya pahami, Anda memiliki fungsi yang menerima 3 argumen, tetapi jika tidak, itu akan berjalan pada perilaku default. Karena Anda belum menjelaskan apa yang harus terjadi ketika 1 atau 2 argumen diberikan, saya akan menganggap itu hanya melakukan perilaku default. Dalam hal ini, saya pikir Anda akan menemukan jawaban berikut sangat menguntungkan:

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

Namun, jika Anda ingin 1 atau 2 argumen ditangani secara berbeda:

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?)

catatan: Ini mengasumsikan bahwa Falsenilai " " tidak akan diteruskan ke metode ini.

Inbar Rose
sumber
memeriksa nilai kebenaran suatu argumen adalah masalah yang berbeda dengan memeriksa apakah suatu argumen ada atau tidak ada
wim
@ wim So mengonversi pertanyaan sesuai dengan jawaban Anda. Modul argparse tidak ada hubungannya dengan pertanyaan, ia menambahkan impor lain, dan jika OP tidak berencana untuk menggunakan argparse sama sekali, itu tidak akan membantu mereka apa pun. Juga, jika "skrip" tidak berdiri sendiri, tetapi sebuah modul, atau suatu fungsi di dalam seperangkat kode yang lebih besar, ia mungkin sudah memiliki parser argumen, dan fungsi khusus ini di dalam skrip yang lebih besar itu bisa menjadi default atau disesuaikan. Karena informasi yang terbatas dari OP, saya tidak bisa tahu bagaimana metode ini harus bertindak, tetapi aman untuk mengasumsikan OP tidak melewati bools.
Inbar Rose
Pertanyaan secara eksplisit mengatakan "Saya memiliki skrip python yang dapat menerima argumen baris perintah nol atau tiga", itu tidak mengatakan "Saya memiliki fungsi yang menerima 3 argumen". Karena modul argparse adalah cara yang disukai untuk menangani argumen baris perintah dengan python, modul ini secara otomatis ada hubungannya dengan pertanyaan. Terakhir, python adalah "termasuk baterai" - tidak ada kerugian dengan "menambahkan impor lain" ketika modul itu merupakan bagian dari perpustakaan standar.
wim
@ wim Pertanyaannya sangat tidak jelas (tubuh tidak cocok dengan judul, misalnya). Saya pikir pertanyaannya tidak cukup jelas bahwa ini adalah jawaban yang valid untuk beberapa interpretasi.
Pasang kembali Monica
2

Jika Anda bekerja dengan iterator kondisi, bisa jadi lambat untuk diakses. Tetapi Anda tidak perlu mengakses setiap elemen lebih dari sekali, dan Anda tidak selalu perlu membaca semuanya. Inilah solusi yang akan bekerja dengan generator yang tak terbatas:

#!/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
sumber
0

Ketika setiap keterberian booladalah True, atau ketika setiap keterberian booladalah False...
mereka semua sama satu sama lain!

Jadi, kita hanya perlu menemukan dua elemen yang mengevaluasi bools yang berbeda
untuk mengetahui bahwa setidaknya ada satu Truedan setidaknya satu False.

Solusi singkat saya:

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

Saya percaya itu hubungan arus pendek, karena AFAIK a==b==csama a==b and b==c.

Solusi umum saya:

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)

Saya juga menulis beberapa kode yang berhubungan dengan beberapa iterables, tetapi saya menghapusnya dari sini karena saya pikir tidak ada gunanya. Namun itu masih tersedia di sini .

GingerPlusPlus
sumber
-2

Ini pada dasarnya adalah fungsi "beberapa (tetapi tidak semua)" (bila dibandingkan dengan any()danall() builtin).

Ini menyiratkan bahwa harus ada Falses dan True s di antara hasilnya. Karena itu, Anda dapat melakukan hal berikut:

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)

Salah satu keuntungan dari kode ini adalah Anda hanya perlu melakukan iterasi satu kali melalui item (boolean) yang dihasilkan.

Salah satu kelemahan adalah bahwa semua kebenaran-ekspresi ini selalu dievaluasi, dan tidak melakukan hubungan arus pendek seperti or/ andoperator.

Abbafei
sumber
1
Saya pikir ini adalah komplikasi yang tidak perlu. Mengapa frozenset bukan set lama polos? Kenapa .issupersetdaripada hanya memeriksa panjang 2, booltidak bisa mengembalikan apa pun selain Benar dan Salah. Mengapa menetapkan lambda (baca: fungsi anonim) untuk nama alih-alih hanya menggunakan def?
wim
1
sintaks lambda lebih logis untuk beberapa. mereka panjangnya sama karena Anda perlu returnjika Anda menggunakan def. Saya pikir secara umum solusi ini bagus. tidak perlu membatasi diri pada boolean, pertanyaannya pada dasarnya adalah "bagaimana cara memastikan semua elemen ini muncul dalam daftar saya". mengapa setjika Anda tidak membutuhkan mutabilitas? lebih banyak kekekalan selalu lebih baik jika Anda tidak membutuhkan kinerja.
Janus Troelsen
@JanusTroelsen Anda tepat sasaran! Ini adalah beberapa alasan mengapa saya melakukannya dengan cara ini; itu membuatnya lebih mudah dan lebih jelas bagi saya. Saya cenderung mengadaptasi Python dengan cara saya mengkode :-).
Abbafei
tetapi tidak akan bekerja pada generator yang tak terbatas: P lihat jawaban saya :) petunjuk:tee
Janus Troelsen
@ JanusTroelsen Saya menyadari ini :-). Saya benar-benar memilikinya sebaliknya (dengan Benar / Salah di set dan iterable di param metode) pada awalnya, tetapi menyadari bahwa ini tidak akan bekerja dengan generator yang tak terbatas, dan pengguna mungkin tidak menyadari (karena fakta ini tidak (Belum) disebutkan dalam dokumen untuk params metode set iterable), dan setidaknya seperti ini jelas bahwa itu tidak akan mengambil iterator tak terbatas. Saya menyadari itertools.teetetapi 1) Saya sedang mencari satu-liner yang sederhana / cukup kecil untuk menjamin copy-paste, 2) Anda sudah memberikan jawaban yang menggunakan teknik itu :-)
Abbafei