简洁的书写方式(a + b == c或a + c == b或b + c == a)


136

是否有更紧凑或Pythonic的方式来编写布尔表达式

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

我想出了

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

但这有点奇怪。


16
更紧凑?可能吧 还有Pythonic吗?不太可能。
chepner

126
帮自己一个将来的忙,并保持原始状态:这是唯一可以立即表明其目的的方法。不要修改它。“简单胜于复杂。”,“可读性很重要”。“如果实施难以解释,那是个坏主意。”

21
Pythonic ==不可读?
nhaarman 2015年

3
@wwii它们并不互相排斥。看到a = 0,b = 0,c = 0;)
Honza Brabec 2015年

1
@phresnel说了什么。与其尝试“简化”表达式,不如将其包装在具有描述性名称的函数中。
Cephalopod 2015年

Answers:


206

如果我们看一下Python的Zen,请强调一下:

提姆·彼得斯(Tim Peters)撰写的《 Python之禅》

美丽胜于丑陋。
显式胜于隐式。
简单胜于复杂。
复杂胜于复杂。
扁平比嵌套更好。
稀疏胜于密集。
可读性很重要。
特殊情况还不足以打破规则。
尽管实用性胜过纯度。
错误绝不能默默传递。
除非明确地保持沉默。
面对模棱两可的想法,拒绝猜测的诱惑。
应该有一种-最好只有一种-显而易见的方法。
尽管除非您是荷兰人,否则一开始这种方式可能并不明显。
现在总比没有好。
虽然从来没有比这更好正确的现在。
如果实现难以解释,那是个坏主意。
如果实现易于解释,则可能是个好主意。
命名空间是一个很棒的主意-让我们做更多这些吧!

最Python化的解决方案是最清晰,最简单和最容易解释的解决方案:

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

更好的是,您甚至不需要了解Python就能理解此代码!就这么简单。这是毫无保留的最佳解决方案。其他一切都是智力上的自慰。

此外,这可能也是性能最佳的解决方案,因为它是所有短路建议中的唯一解决方案。如果为a + b == c,则仅进行一次加法和比较。


11
更好的是,加上一些括号以使意图清晰。
Bryan Oakley

3
目的已经非常清楚,没有括号。括号会使阅读起来更难-为什么优先级已经涵盖了作者,为什么作者仍使用括号呢?
Miles Rout 2015年

1
关于尝试变得过于聪明的另一注:您可能会因未考虑到的缺失条件而引入无法预料的错误。换句话说,您可能认为您的新紧凑型解决方案是等效的,但并非在所有情况下都如此。除非有令人信服的理由进行其他编码(性能,内存限制等),否则清除为准。
罗布·克雷格

这取决于公式的用途。看一下“显式胜于隐式”,可能是“排序优先”方法更清楚地表达了程序在做什么,或者是其中一种。我认为我们不能从这个问题判断。
托马斯·艾勒

101

解决以下三个等式:

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

4
唯一的问题是副作用。如果b或c是更复杂的表达式,它们将运行多次。
Silvio Mayolo 2015年

3
@Kroltan我的意思是我实际上回答了他的问题,该问题要求“更紧凑”的表示形式。参见:en.m.wikipedia.org/wiki/Short-circuit_evaluation
Alex Varga

24
任何阅读此代码的人都可能会诅咒您“聪明”。
Karoly Horvath

5
@SilvioMayolo原著也是如此
-Izkata

1
@AlexVarga,“我的意思是我实际上回答了他的问题”。你做到了 它减少了30%的字符(在运算符之间放置空格)。我并不是要说您的答案是错误的,只是评论它的惯用法(pythonic)。好答案。
Paul Draper 2015年

54

Python具有对序列的所有元素any执行的功能or。在这里,我已经将您的陈述转换为3元素元组。

any((a + b == c, a + c == b, b + c == a))

请注意,这or是短路的,因此,如果计算单个条件的成本很高,则最好保留原始结构。


2
any()all()短路。
TigerhawkT3 2015年

42
@ TigerhawkT3在这种情况下不是这样;这三个表达式将在元组存在之前进行求值,而元组将在any偶数运行之前就存在。

13
知道了 我猜只有在其中有一个生成器或类似的惰性迭代器的时候。
TigerhawkT3 2015年

4
any并且all“短路” 检查给出的可迭代项的过程;但是,如果该可迭代对象是序列而不是生成器,则在函数调用发生之前已经对其进行了全面评估。
Karl Knechtel

这样做的好处是,它很容易分成多行(对语句的参数进行any双缩进,):if语句中进行单缩进),这在涉及数学时有助于提高可读性
Izkata 2015年

40

如果您知道只处理正数,则可以使用,并且很干净:

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

正如我所说,这仅适用于正数;但是,如果您知道它们将是肯定的,那么这是一个非常易读的IMO解决方案,即使直接在代码中而不是在函数中。

您可以执行此操作,这可能需要进行一些重复的计算。但是您没有将性能指定为目标:

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,你知道,我之前注意到了;不知道为什么我没有解决它。
Cyphase

您的最佳解决方案不适用于大量输入,而OP的建议适用于所有输入。“不工作”比“工作”更像Python?
巴里

3
哦,快点 “ 如果您知道只处理正数,那将起作用,而且很干净”。其他所有参数都适用于任何数字,但是如果您知道只处理正数,则最上面的是非常易读的/ Pythonic IMO。
Cyphase

17

如果仅使用三个变量,则使用初始方法:

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

已经很pythonic了。

如果您打算使用更多的变量,那么您的推理方法如下:

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

而这将不得不为任何一个,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
埃里克雷诺夫

谢谢,那是真的,我需要使用Abs。现在进行调整。
ThatGuyRussell 2015年

2
在打了六个abs()电话之后,它比起OP的片段是Python风格的(我实际上称其可读性差得多)。
TigerhawkT3

没错,我现在要调整一下
ThatGuyRussell

1
@ThatGuyRussell为了短路,您可能想使用发电机...之类的东西any(sum(values) == 2*x for x in values),这样您就不必在必要时预先进行所有加倍操作。
巴里

12

以下代码可用于将每个元素与其他元素的总和进行迭代比较,这是根据整个列表的总和(不包括该元素)计算得出的。

 l = [a,b,c]
 any(sum(l)-e == e for e in l)

2
尼斯:)我想,如果您[]从第二行中除去括号,这甚至会与原始行短路,or...
psmears 2015年

1
这基本上any(a + b + c == 2*x for x in [a, b, c])与OP的建议非常接近
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
仅当您希望在多个位置使用同一代码或代码很复杂时,才应编写函数。最初的问题中没有提及代码重用,并且为一行代码编写函数不仅过高,而且实际上会损害可读性。
伊戈尔·莱维奇

5
来自FP事物学校,我几乎不得不完全不同意,并指出众所周知的单行函数是增加可读性的最佳工具。做一个功能,只要你服用做某事的步骤没有立即带来更为清晰的东西,你在做什么,作为函数的名称,可以指定什么比任何评论可能更好。
2015年

无论您采用哪种学校,盲目遵守一套规则都是不好的。必须跳到源代码的另一部分以读取隐藏在函数中的那一行代码,只是为了验证它是否确实按照名称中的说明进行操作,然后必须切换回调用位置确保传递正确的参数是完全不必要的上下文切换。我认为这样做会损害可读性和工作流程。最后,函数名称和代码注释都不能替代代码文档。
伊戈尔·莱维奇

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
由于浮点舍入错误,因此对于某些输入返回错误的结果。
pt

出于性能和准确性的原因,应避免分割。
伊戈尔·莱维基

1
@pts不会因为浮点舍入而导致任何实现返回不正确的结果?甚至a + b == c
osundblad'Aug

@osundblad:如果a,b和c是整数,则(a + b + c)/ 2会四舍五入(并可能返回错误的结果),但是a + b == c是准确的。
pt

3
除以2只是将指数减一,因此对于任何小于2 ^ 53(在python中,float的分数部分)的整数,它都是准确的;对于较大的整数,您可以使用decimal。例如,要检查小于2 ^ 30的整数,请运行[x for x in range(pow(2,30)) if x != ((x * 2)/ pow(2,1))]
Vitalii Fedorenko,2015年

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

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


我当时在想完全相同的想法,但我不能否认他的最初建议更加
简洁

@Mehrdad-当然。确实没有什么不同(a+b<>c) && (a+c<>b) && (b+c<>a) == false
笨拙的2015年

只是乘法比逻辑表达式和基本算术要贵。
伊戈尔·莱维奇

@IgorLevicki-是的,尽管这是非常过早的优化问题。每秒将执行数万次吗?如果是,那么您可能希望查看其他内容。
mbeckish

@mbeckish-您为什么认为它为时过早?代码在编写时应牢记优化,而不是事后才进行优化。有一天,某个实习生将复制此代码段并将其粘贴到嵌入式平台上的某个性能关键循环中,然后该循环将在数百万个设备上运行,这不一定会使其运行缓慢,但可能会浪费更多电池电量。编写此类代码只会鼓励不良的编码做法。我认为,OP应该问的是是否有一种方法可以优化该逻辑表达式。
伊戈尔·莱维奇

6

怎么样:

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

请注意,如果变量是无符号的,这将不起作用。

从代码优化的角度(至少在x86平台上),这似乎是最有效的解决方案。

现代编译器将内联两个abs()函数调用,并通过使用巧妙的CDQ,XOR和SUB指令序列来避免符号测试和随后的条件分支。因此,上面的高级代码将仅由低延迟,高吞吐量的ALU指令和仅两个条件表示。


而且我认为fabs()可以用于float类型;)。
shA.t 2015年

4

Alex Varga提供的解决方案“ a in(b + c,bc,cb)”是紧凑的,并且在数学上很漂亮,但实际上我不会那样写代码,因为下一个开发人员不会立即理解代码的目的。

马克·兰瑟姆(Mark Ransom)的解决方案

any((a + b == c, a + c == b, b + c == a))

比它更清晰,但不比它更简洁

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

当编写代码时,别人不得不去看,或者当我忘记了编写代码时的想法时,我将不得不花很长时间去看,太短或太聪明往往弊大于利。代码应可读。简洁是件好事,但不是那么简洁,以致下一个程序员无法理解。


诚实的问题:为什么人们总是认为下一个程序员将​​是一个无法阅读代码的白痴?我个人觉得这个想法很侮辱。如果必须编写对每个程序员来说显而易见的代码,那么这意味着我们作为一个职业正在迎接最低公分母,也是我们中技术最弱的人。如果我们继续这样做,他们将如何提高个人技能?在其他职业中我看不到这一点。您是什么时候最后一次看到作曲家编写简单的乐谱,以便每个音乐家都可以演奏,而不论其技术水平如何?
伊戈尔·莱维基

6
问题是,即使程序员也只有有限的智力,所以您是否想将有限的智力用于算法和程序的更高层面,或者弄清楚何时可以更简单地表达某些复杂代码行意味着什么? ?编程很困难,所以不要不必要地加重自己的负担,就像奥运会跑步者不会因为背负沉重的背包而参加比赛一样。正如Steve McConell在Code Complete 2中所说的那样,可读性是代码最重要的方面之一。
Paul J Abernathy,2015年

2

请求的是更紧凑或更Pythonic-我尝试了更紧凑。

给定

import functools, itertools
f = functools.partial(itertools.permutations, r = 3)
def g(x,y,z):
    return x + y == z

比原版少2个字符

any(g(*args) for args in f((a,b,c)))

测试:

assert any(g(*args) for args in f((a,b,c))) == (a + b == c or a + c == b or b + c == a)

另外,给出:

h = functools.partial(itertools.starmap, g)

这是等效的

any(h(f((a,b,c))))

好吧,它比原始字符短两个字符,但不是OP后来给出的那个字符,他说他现在正在使用。原始文件还包含许多空白,并在可能的情况下将其忽略。g()为了使此功能正常运行,您还需要定义函数的小问题。考虑到所有这些,我想说它要大得多。
TigerhawkT3 2015年

@ TigerhawkT3,我将其解释为要求使用较短的表达式/行。请参阅编辑以获取进一步的改进
第二次世界大战2015年

4
很差的功能名称,仅适用于代码高尔夫。
2015年

@ 0xc0de-对不起,我不玩。合适的人可能会很主观,并且要视情况而定-但我会顺从社区。
wwii 2015年

当字符比原始代码多时,我看不出它如何紧凑。
伊戈尔·莱维基

1

我想介绍一下我认为是最pythonic的答案:

def one_number_is_the_sum_of_the_others(a, b, c):
    return any((a == b + c, b == a + c, c == a + b))

一般情况,未优化:

def one_number_is_the_sum_of_the_others(numbers):
    for idx in range(len(numbers)):
        remaining_numbers = numbers[:]
        sum_candidate = remaining_numbers.pop(idx)
        if sum_candidate == sum(remaining_numbers):
            return True
    return False 

就Python Zen而言,我认为强调的陈述比其他答案更受关注:

提姆·彼得斯(Tim Peters)撰写的《 Python之禅》

美丽胜于丑陋。
显式胜于隐式。
简单胜于复杂。
复杂胜于复杂。
扁平比嵌套更好。
稀疏胜于密集。
可读性很重要。
特殊情况还不足以打破规则。
尽管实用性胜过纯度。
错误绝不能默默传递。
除非明确地保持沉默。
面对模棱两可的想法,拒绝猜测的诱惑。
应该有一种-最好只有一种-显而易见的方法。
尽管除非您是荷兰人,否则一开始这种方式可能并不明显。
现在总比没有好。
虽然从来没有比这更好正确的现在。
如果实现难以解释,那是个坏主意。
如果实现易于解释,则可能是个好主意。
命名空间是一个很棒的主意-让我们做更多这些吧!


1

作为我编程的一个老习惯,我认为将复杂的表达式放在子句中可以使其更具可读性,如下所示:

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

加号()

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

而且我认为使用多行也可以使更多的感觉像这样:

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

0

以一般方式

m = a+b-c;
if (m == 0 || m == 2*a || m == 2*b) do_stuff ();

如果您可以操作输入变量,

c = a+b-c;
if (c==0 || c == 2*a || c == 2*b) do_stuff ();

如果要使用位hack进行利用,则可以使用“!”,“ >> 1”和“ << 1”

我避免了除法,尽管它可以避免两次乘法以避免舍入错误。但是,检查溢出


0
def any_sum_of_others (*nums):
    num_elements = len(nums)
    for i in range(num_elements):
        discriminating_map = map(lambda j: -1 if j == i else 1, range(num_elements))
        if sum(n * u for n, u in zip(nums, discriminating_map)) == 0:
            return True
    return False

print(any_sum_of_others(0, 0, 0)) # True
print(any_sum_of_others(1, 2, 3)) # True
print(any_sum_of_others(7, 12, 5)) # True
print(any_sum_of_others(4, 2, 2)) # True
print(any_sum_of_others(1, -1, 0)) # True
print(any_sum_of_others(9, 8, -4)) # False
print(any_sum_of_others(4, 3, 2)) # False
print(any_sum_of_others(1, 1, 1, 1, 4)) # True
print(any_sum_of_others(0)) # True
print(any_sum_of_others(1)) # False

仅当您希望在多个位置使用同一代码或代码很复杂时,才应编写函数。最初的问题中没有提及代码重用,并且为一行代码编写函数不仅过高,而且实际上会损害可读性。
伊戈尔·莱维奇

我不同意它会损害可读性;如果您选择合适的名称,则可能会提高可读性(但我对我在此答案中选择的名称的质量不作任何陈述)。另外,给概念起个名字可能会有帮助,只要您强迫自己给函数起好名字,就必须这样做。功能很好。至于功能是否足够复杂,可以从封装在功能中受益,这是一个主观判断。
Hammerite
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.