闰年布尔逻辑:包括括号?

Leap Year Boolean Logic: Include Parentheses?

哪个是"more correct (logically)"? 闰年特有,不泛泛.

  1. 带括号

    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
    
  2. 没有

    return year % 4 == 0 and year % 100 != 0 or year % 400 == 0
    

附加信息

括号更改布尔值的计算顺序(andor w/o 括号之前)。

鉴于此问题中所有较大的数字都可以被较小的数字整除,因此 returns 无论哪种方式都是正确的结果,但我仍然很好奇。

观察括号的效果:

  1. False and True or True
    #True
    
    False and (True or True)
    #False
    
  2. False and False or True
    #True
    
    False and (False or True)
    #False
    

没有括号,有些情况下即使 year 不能被 4 整除(第一个布尔值)它仍然 returns True (我知道在这个问题中这是不可能的)! 被 4 整除不是必须的,因此包含括号更正确吗? 这里还有什么我应该注意的吗?有人可以解释一下 not/including 括号的理论逻辑吗?

括号会影响布尔值的顺序。 and 组合在一起并在 or 之前解析,因此:

a and b or c

变为:

(a and b) or c

如果 ab 都为真,或者如果 c 为真,我们得到 True.

加上括号你得到:

a and (b or c)

现在如果 a 为真且 bc 为真,则得到 True


就"correctness,"而言,只要您的代码得出正确的结果,那么"more correct"只是一个见仁见智的问题。我会在您认为可以使结果更清晰的地方包括括号。例如:

if (a and b) or c:

更清晰
if a and b or c:

但是(在我看来)它并不比:

更清楚
if some_long_identifier and some_other_long_identifier or \
   some_third_long_identifier_on_another_line:

您编写 Python 代码时的指南应该是 PEP8。 PEP8 对何时应该包含文体括号(阅读:遵循自然操作顺序的括号)保持沉默,因此请使用您的最佳判断。


特别是对于闰年,逻辑是:

  1. 如果年份可以被 4 整除,则转到步骤 2。...
  2. 如果年份可以被 100 整除,则转到步骤 3。...
  3. 如果年份可以被 400 整除,则转到步骤 4。...
  4. 该年为闰年(有366天)。
  5. 该年不是闰年(它有 365 天)。

换句话说:所有能被 4 整除的年份都是闰年,除非它们能被 100 整除且不能被 400 整除,即:

return y % 4 == 0 and not (y % 100 == 0 and y % 400 != 0)

Which answer is "more correct" and why?

这不是什么'更正确'的问题,而是;你想实现什么逻辑? boolean 表达式中括号'改变order of operations。这允许您在执行时强制执行优先级。

>>> (True or True) and False  # or expression evaluates first.
False
>>> True or True and False  # and evaluates first.
True

闰年公式中的逻辑rules如下:

  1. 闰年是任何可以被4整除的年份(如2012、2016等)

  2. 除非能被100整除,否则不能整除(如2100、2200等)

  3. 除非能被400整除则为(如2000、2400)

因此例外规则必须优先,这就是 or 周围的括号对于遵守公式规则是必要的。否则 and 的两个参数将首先被评估。

包括括号。在英语中,规则是:

  1. 年份必须能被 4 整除。
  2. 年份不能被 100 整除,除非它可以被 400 整除。

带括号的版本最符合这条双管齐下的规则。

return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
       ^^^^^^^^^^^^^     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            (1)                          (2)

碰巧,删除括号不会破坏代码,但会导致规则版本不自然:

  1. 年份必须能被 4 整除,但不能被 100 整除;或
  2. 年份必须能被 400 整除。

这不是我对闰年规则的看法。

Which answer is "more correct" and why? (Specific to Leap Year Logic, not in general)

With Parentheses

return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

Without

return year % 4 == 0 and year % 100 != 0 or year % 400 == 0

取决于你对“更正确”的定义。如您所知,两者 return 都是正确的。

现在推测“更正确”- 如果您指的是性能优势,考虑到当前的智能编译器,我想不出任何。

如果你是在讨论人类可读性的观点,我会同意,

return year % 4 == 0 and year % 100 != 0 or year % 400 == 0

它自然会缩小范围,这与您的其他选择相反,后者似乎在视觉上包含两个不相交的元素。

我建议,包括括号,但如下所示:

return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0

正如你所指出的,在操作上,没有区别,因为数字被400整除意味着它也可以被100整除,这意味着它也可以被4整除。操作上,括号是否有任何效果取决于语言的词汇顺序(求值顺序)。今天的大多数语言都遵循 c 的约定,这意味着运算符的指定优先级,否则是从左到右。如有疑问,我总是会加上括号以提高可读性。

从文体上来说,这种东西放在这么长的表达式中很难读懂。如果它必须是一个表达式,我宁愿使用逻辑 "sum of products" 而不是 "product of sums" 所以我会去

return (year%400 == 0) or (year%100 != 0 and year%4 == 0)

甚至

bool IsLeap = false;
if (year%4 == 0) IsLeap = true;
if (year%100 == 0) IsLeap = false;
if (year%400 == 0) IsLeap = true;

return IsLeap;

无论如何,优化的编译器会产生高效的代码,这种东西确实有助于像我这样的穷人阅读它。

答案:包括括号


John Kugelman 解释了为什么它们是 2 个独立的逻辑测试而不是 3 个,-> 最后 2 个应该组合在一起:

  1. 年份必须能被 4 整除。
  2. (2) 年份不能被 100 可见,(3) 除非它能被 400 整除。

带括号的版本最符合这条双管齐下的规则。

return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
       ^^^^^^^^^^^^^     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            (1)                          (2)

碰巧,删除括号不会破坏代码,但会导致规则版本不自然:

  1. 年份必须能被 4 整除,但不能被 100 整除;或
  2. 年份必须能被 400 整除。

这不是我对闰年规则的看法。


mrdomoboto启发,100/400是例外!:

年份必须能被4整除,100是个例外,400是例外中的例外,但他们加起来还是一个例外(见上文)。这意味着如果年份不能被 4 整除,那么整个事情一定是假的。确保这一点的唯一方法是将异常放在括号中,因为 False and bool 将始终 return False.

请参阅以下来自 JBallin

的示例
  1. False and True or True
    #True
    
    False and (True or True)
    #False
    
  2. False and False or True
    #True
    
    False and (False or True)
    #False
    

Adam Smith 将英文翻译成代码:

所有能被 4 整除的年份都是闰年,除非它们能被 100 整除且不能被 400 整除,即:

return y % 4 == 0 and not (y % 100 == 0 and y % 400 != 0)

JBallin 引用了 德摩根定律:

not(a and b) = (not a or not b)

要将上面的内容转换成所需的答案:

#convert using "DML"
return y % 4 == 0 and (not y % 100 == 0 or not y % 400 != 0)
#remove "not"s by switching "==" and "!="
return y % 4 == 0 and (y % 100 != 0 or y % 400 == 0)

这是另一个区别:速度和效率。

除了评估顺序(已在其他答案中提及)...

让我们简化原始表达式 year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) 到此:

A and (B or C)

如果A为假,则没有理由测试BC,因为and要求双方都是true

短路操作员

逻辑运算符 andor 在许多语言中具有 "short-circuit" 效果,包括 Python,其中仅计算左侧(参见 https://docs.python.org/2/library/stdtypes.html#boolean-operations-and-or-not):

  • and左边为false时短路(因为右边不能使结果为true
  • or左边为true时短路(因为右边不能使结果为false

带括号:

A and (B or C)

  • Afalse 时,右侧 (B or C) 不被计算,节省了 CPU 资源。
  • Atrue 时,B 被计算,但 C 仅在 B 为假时被计算。

没有括号:

A and B or C

  • Afalse 时,B 未被计算,但 C 被(不必要地)计算。
  • Atrue 时,B 被计算。如果 BfalseC 也会计算。

结论: 如果没有括号,当 A 为假(year % 4 测试)时,Cyear % 400 测试)会被不必要地求值。这是 CPU 可以停在 A 的时间的 75%,但会继续做更多不必要的数学运算。

最高效的表达式:

改用这个: ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0))

此表达式在两种情况下将模(慢除法)替换为按位与(快!)。

更多详细信息: