ChatGPT解决这个技术问题 Extra ChatGPT

“if a or b or c but not all of them”的Python语法

我有一个可以接收零个或三个命令行参数的 python 脚本。 (它要么以默认行为运行,要么需要指定所有三个值。)

什么是理想的语法,例如:

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

?

可能从 `if len(sys.argv) == 0 之类的东西开始:
@EdgarAroutiounian len(sys.argv) 将始终至少为 1:它将可执行文件包含为 argv[0]
问题的正文与问题的标题不匹配。您是否要检查“如果 a 或 b 或 c 但不是全部”或“如果 a、b 和 c 中的一个”(如您给出的表达式)?
你对 a+b+c 有什么想说的?
等等,问题,它可以接受零个或三个参数。你不能只说if not (a and b and c)(零个参数),然后说if a and b and c(所有三个参数)吗?

S
Stefano Sanfilippo

如果您的意思是最小形式,请使用:

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

这翻译了你的问题的标题。

更新:正如 Volatility 和 Supr 正确所说,您可以应用德摩根定律并获得等价物:

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

我的建议是使用对您和其他程序员更重要的形式。第一个意思是“有些东西是假的,但也是真的”,第二个意思是“有些东西是真的,但不是一切”。如果我要在硬件中进行优化或执行此操作,我会选择第二个,这里只选择最易读的(还要考虑您将要测试的条件及其名称)。我选了第一个。


所有的答案都很好,但这会以简洁性为优势,而且短路很大。谢谢大家!
我会让它更简洁,并使用 if not (a and b and c) and (a or b or c)
甚至 if (a or b or c) and not (a and b and c) 以完美匹配标题;)
@HennyH我相信这个问题要求“至少一个条件为真但不是全部”,而不是“只有一个条件为真”。
@Supr if any([a,b,c]) and not all([a,b,c])
d
defuz

怎么样:

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

其他变体:

if 1 <= sum(map(bool, conditions)) <= 2:
   ...

如果其中任何一个返回 2,例如 True,则 sum(conditions) 可能会出错。
确实,您需要一个丑陋的 sum(map(bool, conditions))
请注意,这不是短路,因为所有条件都是预先评估的。
@PaulScheltema 第一种形式对任何人来说都更容易理解。
这种“任何而不是全部”是布尔方法中最好和最清晰的,请注意存在的 arg 和“真实”的 arg 之间的重要区别
w
wim

这个问题已经有很多被高度评价的答案和一个被接受的答案,但到目前为止,他们都被各种表达布尔问题的方式分散了注意力,错过了一个关键点:

我有一个可以接收零个或三个命令行参数的 python 脚本。 (要么以默认行为运行,要么需要指定所有三个值)

这个逻辑首先不应该是库代码的责任,而应该由命令行解析(通常是 Python 中的 argparse 模块)处理。不要费心编写复杂的 if 语句,而是更喜欢像这样设置参数解析器:

#!/usr/bin/env python
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--foo', nargs=3, default=['x', 'y', 'z'])
args = parser.parse_args()

print(args.foo)

是的,它应该是一个选项而不是位置参数,因为它毕竟是可选的。

已编辑: 为了解决评论中 LarsH 的担忧,下面是一个示例,说明如果您确定要使用 3 或 0 的接口,如何编写它 < em>位置参数。我认为以前的界面风格更好(因为 optional 参数应该是 options),但为了完整起见,这里有另一种方法。请注意,我们在创建解析器时会覆盖 kwarg usage,否则 argparse 会自动生成误导性使用消息!

#!/usr/bin/env python
import argparse

parser = argparse.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)

以下是一些使用示例:

# default case
$ ./three_or_none.py 
['x', 'y', 'z']

# explicit case
$ ./three_or_none.py 1 2 3
['1', '2', '3']

# example failure mode
$ ./three_or_none.py 1 2 
usage: three_or_none.py [-h] [a b c]
three_or_none.py: error: expected 3 arguments

是的,我是故意添加的。可以使参数定位,并强制使用 3 或 0,但它不会成为一个好的 CLI,所以我不推荐它。
单独的问题。你不相信它是好的 CLI,你可以为这一点争论,并且 OP 可能会被说服。但是您的答案明显偏离了问题,以至于需要提及规范的更改。您似乎在弯曲规范以适应可用工具,而没有提及更改。
@LarsH 好的,我添加了一个更适合问题中隐含的原始界面的示例。现在它正在弯曲工具以满足可用的规格......;)
这是我赞成的唯一答案。 +1 回答真正的问题。
+1。 CLI 的形式是一个重要的切线问题,不像另一个人所说的那样完全分开。我和其他人一样赞成您的帖子-您的帖子解决了问题的根源并提供了优雅的解决方案,而其他帖子则回答了字面问题。这两种答案都很有用,值得+1。
J
Jon Clements

我会去:

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

我认为这应该相当有效地短路

解释

通过使 conds 成为迭代器,第一次使用 any 将短路并使迭代器指向下一个元素(如果任何项目为真);否则,它将消耗整个列表并成为 False。下一个 any 获取可迭代项中的剩余项,并确保没有任何其他真值...如果有,则整个语句不能为真,因此没有一个唯一元素 (所以再次短路)。最后一个 any 将返回 False 或耗尽可迭代对象并成为 True

注意:以上检查是否只设置了一个条件

如果您想检查一个或多个项目,但不是每个项目都已设置,那么您可以使用:

not all(conds) and any(conds)

我不明白。它读起来像:如果真而不是真。帮我理解。
@rGil:读起来像“如果有些苹果是红色的,而有些不是”-这与说“有些苹果是红色的,但不是全部是红色的”是一样的。
即使有解释,我也无法理解行为...使用 [a, b, c] = [True, True, False],您的代码不应该“打印”False,而预期的输出是 True
这非常聪明,但是:如果您不知道预先有多少条件,我会使用这种方法,但是对于固定的、已知的条件列表,失去可读性根本不值得。
这不会短路。该列表在传递给 iter 之前已完全构建。 anyall 会懒惰地消耗列表,没错,但是当您到达那里时,列表已经被完全评估了!
K
Kaz

英文句子:

“如果 a 或 b 或 c 但不是全部”

翻译成这个逻辑:

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

“但是”这个词通常意味着一个连词,也就是“和”。此外,“所有这些”转化为条件的结合:这个条件、那个条件和其他条件。 “不”反转了整个连词。

我不同意接受的答案。作者忽略了对规范应用最直接的解释,也忽略了应用德摩根定律将表达式简化为更少的运算符:

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

同时声称答案是“最小形式”。


实际上,这种形式是最小的。这是表达式的最小 PoS 形式。
C
Casimir et Hippolyte

怎么样:(独特的条件)

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

请注意,如果您也允许两个条件,则可以这样做

if (bool(a) + bool(b) + bool(c) in [1,2]):

为了记录,问题要求两个条件。至少一个,但不是全部 = 1 of all 或 2 of all
恕我直言,您应该将第二个拼写为 1 <= bool(a) + bool(b) + bool(c) <= 2
e
eumiro

如果三个条件中的一个且只有一个是 True,则返回 True。可能是您在示例代码中想要的。

if sum(1 for x in (a,b,c) if x) == 1:

不如@defuz 的答案漂亮
这将检查其中一个条件是否为真。如果其中两个条件为真,则返回 False,而如果两个条件为真,则“a or b or c but not all of them”应该返回 True。
D
Danubian Sailor

需要明确的是,您想根据有多少参数是逻辑 TRUE(如果是字符串参数 - 不为空)来做出决定?

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

然后你做了一个决定:

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

现在逻辑更清楚了。


L
Louis

为什么不数数呢?

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

S
Spinno

如果您不介意有点神秘,您可以简单地使用 0 < (a + b + c) < 3 滚动,如果您有一到两个正确的陈述,则返回 true,如果全部为假或没有一个为假,则返回假。

如果您使用函数来评估布尔值,这也会简化,因为您只评估一次变量,这意味着您可以内联编写函数而无需临时存储变量。 (示例:0 < ( a(x) + b(x) + c(x) ) < 3。)


R
Relaxing In Cyprus

该问题表明您需要所有三个参数(a 和 b 和 c)或不需要所有三个参数(不是(a 或 b 或 c))

这给出了:

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


I
Inbar Rose

据我了解,您有一个接收 3 个参数的函数,但如果没有,它将以默认行为运行。由于您没有解释提供 1 或 2 个参数时会发生什么,我假设它应该只是执行默认行为。在这种情况下,我认为您会发现以下答案非常有利:

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

但是,如果您希望以不同方式处理 1 或 2 个参数:

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

注意: 这假定“False”值不会传递到此方法中。


检查参数的真值与检查参数是否存在是不同的
@wim So 正在转换问题以适合您的答案。 argparse 模块与问题无关,它添加了另一个导入,如果 OP 根本不打算使用 argparse,它不会帮助他们。此外,如果“脚本”不是独立的,而是一个模块或更大代码集中的一个函数,它可能已经有一个参数解析器,并且该更大脚本中的这个特定函数可以是默认的或自定义的。由于来自 OP 的信息有限,我不知道该方法应该如何操作,但可以安全地假设 OP 没有传递布尔值。
问题明确地说“我有一个可以接收零个或三个命令行参数的python脚本”,它没有说“我有一个接收3个参数的函数”。由于 argparse 模块是在 python 中处理命令行参数的首选方式,它自动与问题有关。最后,python 是“包含电池”的——当该模块是标准库的一部分时,“添加另一个导入”没有任何缺点。
@wim这个问题很不清楚(例如,正文与标题不匹配)。我认为这个问题不够清楚,以至于这是对其进行某些解释的有效答案。
J
Janus Troelsen

如果您使用条件迭代器,访问速度可能会很慢。但是您不需要多次访问每个元素,并且您并不总是需要阅读所有元素。这是一个适用于无限生成器的解决方案:

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

G
GingerPlusPlus

当每个给定的 boolTrue 时,或者当每个给定的 boolFalse 时...
它们都彼此相等!

因此,我们只需要找到两个评估为不同 bools
的元素即可知道至少有一个 True 和至少一个 False

我的简短解决方案:

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

我相信它会短路,因为 AFAIK a==b==c 等于 a==b and b==c

我的通用解决方案:

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)

我还写了一些处理多个迭代的代码,但我从这里删除了它,因为我认为它没有意义。但是它仍然可用here


A
Abbafei

这基本上是“一些(但不是全部)”功能(与 any()all() 内置函数相比)。

这意味着结果中应该有 Falses Trues。因此,您可以执行以下操作:

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)

此代码的一个优点是您只需要遍历生成的(布尔值)项目一次。

一个缺点是所有这些真值表达式总是被评估,并且不像 or/and 运算符那样做 short-circuiting


我认为这是不必要的复杂化。为什么是frozenset而不是普通的旧set?为什么 .issuperset 而不仅仅是检查长度 2,bool 无论如何都不能返回 True 和 False 以外的任何东西。为什么将 lambda(读取:匿名函数)分配给名称而不是仅使用 def?
lambda 语法对某些人来说更合乎逻辑。无论如何,它们的长度相同,因为如果您使用 def,则需要 return。我认为这个解决方案的普遍性很好。没有必要将自己限制为布尔值,问题本质上是“我如何确保所有这些元素都出现在我的列表中”。如果您不需要可变性,为什么要 set?如果您不需要性能,则更多的不变性总是更好。
@JanusTroelsen 你是对的!这些是我这样做的一些原因;它让我更容易和更清楚。我倾向于使 Python 适应我的编码方式:-)。
但它不适用于无限生成器:P 请参阅我的答案 :) 提示:tee
@JanusTroelsen 我意识到这一点:-)。起初我实际上有相反的方式(在集合中使用 True/False,在方法参数中使用可迭代),但意识到这不适用于无限生成器,并且用户可能没有意识到(因为这个事实不是(然而)在可迭代集合方法参数的文档中提到),至少像这样很明显它不会采用无限迭代器。我知道 itertools.tee 但 1)我正在寻找一个简单/足够小以保证复制粘贴的单行,2)您已经给出了使用该技术的答案 :-)