【问题标题】:Leap Year Boolean Logic: Include Parentheses?闰年布尔逻辑:包括括号?
【发布时间】:2016-12-21 23:29:33
【问题描述】:

哪个是“更正确(逻辑上)”? 特定于闰年,而不是一般

  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 之前,没有括号)。

考虑到在这个问题中所有较大的数字都可以被较小的数字整除,无论哪种方式它都会返回正确的结果,但我仍然很好奇。

观察括号的效果:

  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 整除(第一个 bool)它仍然返回 True(我知道这在这个问题中是不可能的)! 不是必须被 4 整除,因此包含括号更正确吗?还有什么我应该注意的吗?有人可以解释不/包括括号的理论逻辑吗?

【问题讨论】:

  • 括号改变了评估顺序,而不是默认的从左到右。因此,在种植它们之前,您实际上必须有更改顺序的理由
  • 发表评论并不意味着我投了反对票。

标签: python boolean-logic parentheses leap-year


【解决方案1】:

括号会影响布尔值的顺序。 ands 被组合在一起并在 ors 之前被解决,所以:

a and b or c

变成:

(a and b) or c

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

用括号你得到:

a and (b or c)

如果a 都是真实的,并且bc 都是真实的,那么现在你会得到True


就“正确性”而言,只要您的代码得出正确的结果,那么“更正确”只是一个见仁见智的问题。我会在你觉得它使结果更清晰的地方加入括号。例如:

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)

【讨论】:

  • 已经在“附加信息”中说明,不回答问题:专门询问闰年问题,不是一般
  • @JBallin 那么我认为你应该重申你的问题,因为这完全回答了所提出的问题。
  • 最后一行显示了我如何在逻辑上分离(b 或 c)!还有——De Morgan's Laws: not (a and b) = not a or not b
  • @JBallin 谈话似乎有点迂腐了。正确的代码是 A) 产生正确输出的代码,B) 在其功能的性能基准范围内这样做,C) 产生的技术债务最少。您在此语句中放置括号的位置完全不会影响这些事情,而德摩根定律是数学证明,而不是 Python 的风格指南。
  • 这是一个理论/逻辑问题(与实践相比)。我引用 DML 是因为你的最后一行加上 DML 是我一直在寻找的逻辑桥梁 - 想分享给未来的读者!
【解决方案2】:

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

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

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

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

事实上,删除括号并不会破坏代码,但会导致不自然的规则版本:

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

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

【讨论】:

    【解决方案3】:

    哪个答案“更正确”,为什么?

    这不是'更正确'的问题,而是;你想实现什么逻辑? 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 的两个参数将首先被评估。

    【讨论】:

    • 我在“附加信息”中说明了这一点
    • @JBallin 更新了更多逻辑解释。
    • 你是说:“(被 4 整除)AND(不例外)”?
    • @JBallin 所以我们知道年份必须能被 4 整除,这是表达式的第一部分。然后我们知道,如果它不是被 100 整除,那么如果规则 1 成立,它也可能是闰年。因此,无论如何,我们都需要规则 1 为真,规则 2 和 3 中的任何一个都为真,以使整个表达式为真。因此,整个表达式 -> (#1) and (not #2 or 3)。
    【解决方案4】:

    哪个答案“更正确”,为什么? (特定于闰年逻辑,不是一般的)

    带括号

    返回年份 % 4 == 0 和(年份 % 100 != 0 或年份 % 400 == 0)

    没有

    返回年份 % 4 == 0 和年份 % 100 != 0 或年份 % 400 == 0

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

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

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

    返回年份 % 4 == 0 和年份 % 100 != 0 或年份 % 400 == 0

    它自然地缩小了范围,与您的其他替代方案相反,它似乎包括视觉上两个不相交的元素。

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

    返回(年 % 4 == 0 和年 % 100 != 0)或年 % 400 == 0

    【讨论】:

      【解决方案5】:

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

      从文体上讲,这种东西放在这样长的表达中很难读懂。如果它必须是一个表达式,我更喜欢逻辑上的“产品总和”到“总和的产品”所以我会去

      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;
      

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

      【讨论】:

        【解决方案6】:

        答案:包括括号


        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 是例外但它们总共仍然是一个例外(见上文)。这意味着如果 year 不能被 4 整除,那么整个事情一定是 False。确保这一点的唯一方法是在异常周围加上括号,因为False and bool 将始终返回 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引用De Morgan's Laws

        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)
        

        【讨论】:

        • 括号允许python的短路AND运算符(docs.python.org/2/library/…)节省CPU时间。如果y % 4 为假,则不会发生y % 400 计算。另外,去掉一些模块(除法)数学......改用这个:((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0))stackoverflow.com/a/11595914/733805
        【解决方案7】:

        还有另一个区别:速度和效率。

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

        让我们简化原来的表达式 year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)这个:

        A and (B or C)

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

        短路操作员

        逻辑运算符 andor 在包括 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))

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

        更多详情请访问:https://stackoverflow.com/a/11595914/733805

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-14
          • 1970-01-01
          • 1970-01-01
          • 2012-10-01
          • 2011-01-08
          • 2015-02-22
          相关资源
          最近更新 更多