简洁的写法 (a + b == c 或 a + c == b 或 b + c == a)

136

有没有更紧凑或更符合Python风格的写布尔表达式的方法?

a + b == c or a + c == b or b + c == a

我想出了

a + b + c in (2*a, 2*b, 2*c)

但那有点奇怪。


16
更紧凑?可能。更符合Python特色?不太可能。 - chepner
129
给你未来的自己一个好处,保持原始形式:这是唯一立即传达目的的方式。不要修改它。 “简洁比复杂更好。”,“易读性很重要。”,“如果实现难以解释,那就是个坏主意。” - poke
22
Pythonic == 难以理解的吗? - nhaarman
3
它们并不是互相排斥的。看看当a=0,b=0,c=0时就能明白了 ;) - Honza Brabec
1
就像@phresnel所说的一样。不要试图“简化”表达式,而是将其封装在一个具有描述性名称的函数中。 - Cephalopod
显示剩余9条评论
17个回答

206

如果我们看一下“Python之禅”,强调如下:

《Python之禅》,作者Tim Peters

优美胜于丑陋。
显式胜于隐式。
简洁胜于复杂。
复杂胜于凌乱。
扁平胜于嵌套。
疏远胜于密集。
可读性很重要。
特殊情况并不足以打破规则。
虽然实用性比纯粹性更重要。
错误不应该悄无声息地传递。
除非有意地保持沉默。
面对模棱两可,拒绝猜测的诱惑。
最好只有一种明显的方法来做事情。
尽管这种方法可能不会一开始就显而易见,除非你是荷兰人。
现在开始总比永远不开始要好。
尽管永远比现在好。
如果实现很难解释,那么这是一个坏主意。
如果实现很容易解释,那么这可能是一个好主意。
命名空间是一个伟大的想法——让我们做更多这样的事情!

最Pythonic的解决方案是最清晰、最简单、最易于解释的方案:

a + b == c or a + c == b or b + c == a

更好的是,您甚至不需要了解Python就能理解这段代码!它非常容易。毫无保留地说,这是最佳解决方案。其他任何方案都是知识上的妄想。

此外,这很可能也是最佳表现的解决方案,因为它是所有提案中唯一的短路。如果a + b == c,只需进行一次加法和比较即可。


12
更好的是,加入一些括号以使意图更加清晰明确。 - Bryan Oakley
3
意图已经非常明确,不需要加上括号。加上括号反而会让阅读更困难——既然优先级已经覆盖了这个问题,为什么作者还要使用括号呢? - Miles Rout
1
关于试图过于聪明的另一个注意事项:您可能会因为忽略了未考虑的条件而引入无法预料的错误。换句话说,您可能认为您的新的简洁解决方案是等效的,但在某些情况下并不是这样。除非有充分的理由要以其他方式编写代码(例如性能、内存限制等),否则清晰易懂为上。 - Rob Craig
这取决于公式的用途。看看“显式优于隐式”,也许“先排序”方法更清楚地表达了程序正在做什么,或者其他方法更好。我认为我们无法从问题中判断。 - Thomas Ahle
“任何其他的都是智力自慰。” - 303

101

解三元一次方程组,求得a的值:

a in (b+c, b-c, c-b)

4
唯一的问题是副作用。如果b或c是更复杂的表达式,它们将被运行多次。 - Silvio Mayolo
3
我的观点是我实际上回答了他的问题,他要求“更紧凑”的表示方法。请参阅:https://en.m.wikipedia.org/wiki/Short-circuit_evaluation - Alex Varga
26
阅读此代码的任何人可能会因为你过于“巧妙”而发出咒骂。 - Karoly Horvath
5
同样的道理适用于原始版本。 - Izkata
1
@AlexVarga,“我的意思是我实际上回答了他的问题”。你确实回答了,使用了30%更少的字符(在运算符之间放置空格)。我并不是想说你的答案是错误的,只是评论一下它有多么惯用(Pythonic)。很好的答案。 - Paul Draper
显示剩余2条评论

54

Python有一个any函数,它对序列的所有元素进行逻辑“或”操作。在这里,我将您的语句转换为一个由三个元素组成的元组。

any((a + b == c, a + c == b, b + c == a))
请注意,or是短路的,因此如果计算各个条件的成本很高,最好保留原始结构。

2
any()all()也会短路。 - TigerhawkT3
45
不过在这种情况下不是这样的;三个表达式将在元组存在之前被计算,元组将在any运行之前就已经存在了。 - poke
13
啊,我明白了。我猜只有在其中有生成器或类似的惰性迭代器时才会出现这种情况。 - TigerhawkT3
5
anyall在处理给定的可迭代对象时会进行"短路",不过如果该可迭代对象是一个序列而非生成器,那么在函数调用发生前它已经被完全评估。 - Karl Knechtel
我猜键盘上的括号种类不够多,所以Python没有生成器字面量;-) - Steve Jessop
显示剩余5条评论

40

如果你知道你只处理正数,这将有效且非常简洁:

a, b, c = sorted((a, b, c))
if a + b == c:
    do_stuff()

就像我之前所说,这个方法只适用于正数;但是如果你知道它们将是正数,那么我认为这是一个非常易读的解决方案,甚至可以直接写在代码中而不是写成一个函数。

你可以尝试这样做,但可能会进行一些重复计算;但既然你没有将性能作为目标,那就无妨:

from itertools import permutations

if any(x + y == z for x, y, z in permutations((a, b, c), 3)):
    do_stuff()

或者不使用permutations()函数并且避免重复计算:

if any(x + y == z for x, y, z in [(a, b, c), (a, c, b), (b, c, a)]:
    do_stuff()

我可能会将这个或其他解决方案放入一个函数中。然后您可以在代码中干净地调用该函数。

个人而言,除非我需要更多的代码灵活性,否则我会只使用您提出的第一种方法。它简单有效。但我仍然可能会将其放入一个函数中:

def two_add_to_third(a, b, c):
    return a + b == c or a + c == b or b + c == a

if two_add_to_third(a, b, c):
    do_stuff()

这相当Pythonic,也很可能是最有效的方法(除了额外的函数调用以外);尽管你不应该过于担心性能,除非它实际上造成了问题。


特别是如果我们可以假设a、b、c都是非负的。 - cphlewis
我觉得“不总是有效”这个短语有点令人困惑。第一种解决方案只适用于确定您的数字为非负数的情况。例如,对于(a,b,c)=(-3,-2,-1),您有a + b!= c但是b + c = a。还有类似的情况,如(-1,1,2)和(-2,-1,1)。 - usernumber
@usernumber,你知道吗,我之前注意到了这个问题,不确定为什么我没有修复它。 - Cyphase
你的最优解决方案对于大量输入无效,而提问者的建议适用于所有输入。在 Python 中,“不工作”比“工作”更符合语言惯例吗? - Barry
3
哎呀,Snap。 "如果你确定只处理正数,那么这个方法可行且很简洁"。其他方法适用于任何数字,但是 "如果你确定只处理正数",我认为顶部的方法可读性/Pythonic性非常好。 - Cyphase

16

如果您只会使用三个变量,那么您最初的方法:

a + b == c or a + c == b or b + c == a

已经非常符合Python风格。

如果您计划使用更多的变量,则需要调整您的思考方式:

a + b + c in (2*a, 2*b, 2*c)

很聪明,但让我们思考为什么。这是为什么起作用呢?
通过一些简单的算术运算,我们可以看到:

a + b = c
c = c
a + b + c == c + c == 2*c
a + b + c == 2*c

这将对于a、b或c中的任何一个都成立,这意味着它将等于2*a2*b2*c。这对于任何数量的变量都是正确的。

因此,快速编写此内容的好方法是简单地列出您的变量,并将它们的总和与加倍值列表进行比较。

values = [a,b,c,d,e,...]
any(sum(values) in [2*x for x in values])

这种方法,要将更多的变量加入方程中,你只需要通过编辑值列表添加'n'个新变量,而不是编写'n'个方程。


4
如果 a=-1b=-1c=-2,那么 a+b=c,但是 a+b+c=-42*max(a,b,c)=-2 - Eric Renouf
2
在加入了半打abs()调用后,它比OP的片段更具Pythonic风格(实际上我认为它的可读性显著降低了)。 - TigerhawkT3
非常正确,我现在会进行调整。 - ThatGuyRussell
抱歉,打错了:*这不太符合 Python 的风格。 - TigerhawkT3
1
@ThatGuyRussell 为了进行短路计算,您需要使用生成器...类似于any(sum(values) == 2*x for x in values),这样您就不必一开始就把所有的加倍都做完,只有在必要时才会执行。 - Barry
显示剩余7条评论

12
以下代码可用于迭代地将每个元素与其他元素的和进行比较,该和是从整个列表的总和计算而来,但不包括该元素本身。
 l = [a,b,c]
 any(sum(l)-e == e for e in l)

2
不错 :) 我认为如果你从第二行中删除 [] 括号,这将像原始代码一样使用 or 进行短路运算... - psmears
1
基本上是 any(a + b + c == 2*x for x in [a, b, c]),与原帖作者的建议非常接近。 - njzk2
这很相似,但是这种方法适用于任意数量的变量。我采纳了@psmears关于短路的建议。 - Arcanum

10

不要试图简化它。相反,通过函数命名来描述你正在做什么:

def any_two_sum_to_third(a, b, c):
  return a + b == c or a + c == b or b + c == a

if any_two_sum_to_third(foo, bar, baz):
  ...

用一些“聪明”的东西替换条件可能会使它更短,但不会使它更易读。然而,将它保留原样也不够易读,因为很难一眼看出你为什么要检查这三个条件。这种方法可以绝对清楚地说明你正在检查什么。

关于性能,这种方法确实增加了函数调用的开销,但除非你找到了必须解决的瓶颈,否则不要为了性能而牺牲可读性。并且始终进行度量,因为某些聪明的实现可以优化掉并内联一些函数调用。


1
如果你期望在多个位置使用相同的代码,或者代码很复杂,那么应该编写函数。原始问题没有提到代码重用,为单行代码编写函数不仅过度设计,而且实际上会影响可读性。 - Igor Levicki
5
作为一位来自函数式编程领域的人,我完全不同意这种观点,并且要说出来:给函数命名清晰易懂是增加代码可读性最好的工具之一。当你在执行某些步骤时,如果不能立即清晰地表达你在做什么,那么就应该创建一个函数。函数名称比注释更能准确描述你在做什么。 - Jack
无论你信奉哪个学派,盲目遵循一套规则都是不好的。为了验证一个函数内部隐藏的一行代码是否真正实现了它所说的功能,而不得不跳到源代码的另一个部分,然后再回到调用处确保传递正确的参数,这种上下文切换完全是不必要的。在我看来,这样做会影响代码的可读性和工作流程。最后,函数名称和代码注释都不能替代代码文档。 - Igor Levicki

9

Python 3:

(a+b+c)/2 in (a,b,c)
(a+b+c+d)/2 in (a,b,c,d)
...

它可以适应任意数量的变量:

arr = [a,b,c,d,...]
sum(arr)/2 in arr

然而,总的来说,我认为除非你有三个以上的变量,原始版本更易读。

3
由于浮点数舍入误差,这将导致某些输入返回不正确的结果。 - pts
出于性能和准确性的原因,应避免使用除法。 - Igor Levicki
1
@pts 任何实现都会因为浮点数舍入而返回不正确的结果吗?即使a+b==c。 - osundblad
@osundblad:在Python 3中,除以2.0与除以2相同。它们都执行浮点数除法,这种方法容易出现误差,并且可能由于四舍五入而返回不正确的结果。 - pts
3
除以2就是将指数减1,因此对于任何小于2^53(Python浮点数的小数部分)的整数都是准确的。对于更大的整数,您可以使用decimal。例如,要检查小于2^30的整数,请运行[x for x in range(pow(2,30)) if x != ((x * 2)/ pow(2,1))] - Vitalii Fedorenko
显示剩余2条评论

6
(a+b-c)*(a+c-b)*(b+c-a) == 0

如果任意两个术语的和等于第三个术语,则其中一个因数将为零,从而使整个乘积为零。


我也是这么想的,但我不能否认他的原始提议更加简洁... - user541686
@Mehrdad - 当然。这与(a+b<>c) && (a+c<>b) && (b+c<>a) == false没有什么不同。 - mbeckish
只是因为乘法比逻辑表达式和基本算术更耗费资源。 - Igor Levicki
@IgorLevicki - 是的,虽然这是一个非常早期的优化问题。它会每秒执行成千上万次吗?如果是的话,那么你可能需要考虑其他方案了。 - mbeckish
@mbeckish - 你为什么认为现在优化为时过早?代码应该考虑到优化,而不是事后进行优化。总有一天,某个实习生会将此代码片段复制并粘贴到某个性能关键循环中,并在数百万设备上运行,这些设备可能不一定因其功能而变慢,但可能会浪费更多的电池电量。编写这样的代码只会鼓励不良的编码实践。在我看来,OP应该问的是是否有一种优化逻辑表达式的方法。 - Igor Levicki
@IgorLevicki - 我同意,那才是原帖应该问的问题。这会导致不同的答案。 - mbeckish

6

只是这样怎么样:

a == b + c or abs(a) == abs(b - c)

注意,如果变量是无符号的,这种方法将不起作用。
从代码优化的角度来看(至少在x86平台上),这似乎是最有效的解决方案。
现代编译器将内联abs()函数调用,并通过使用一系列聪明的CDQ、XOR和SUB指令避免标志测试和随后的条件分支。因此,上面的高级代码将只被表示为低延迟、高吞吐量的ALU指令和只有两个条件分支。

我认为fabs()可以用于float类型 ;)。 - shA.t

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接