【问题标题】:Python elegant assignment based on True/False values基于 True/False 值的 Python 优雅赋值
【发布时间】:2011-06-11 05:57:28
【问题描述】:

我想根据三个布尔值中的值设置一个变量。最直接的方式是 if 语句后跟一系列 elif:

if a and b and c:
    name = 'first'
elif a and b and not c:
    name = 'second'
elif a and not b and c:
    name = 'third'
elif a and not b and not c:
    name = 'fourth'
elif not a and b and c:
    name = 'fifth'
elif not a and b and not c:
    name = 'sixth'
elif not a and not b and c:
    name = 'seventh'
elif not a and not b and not c:
    name = 'eighth'

这有点尴尬,我想知道是否有更 Pythonic 的方式来处理这个问题。想到了几个想法。

  1. 字典破解:

    name = {a and b and c: 'first',
            a and b and not c: 'second',
            a and not b and c: 'third',
            a and not b and not c: 'fourth',
            not a and b and c: 'fifth',
            not a and b and not c: 'sixth',
            not a and not b and c: 'seventh',
            not a and not b and not c: 'eighth'}[True]
    

我称其为 hack,因为我对其中 7 个键为 False 并相互覆盖并不太感兴趣。

  1. 和/或魔法

    name = (a and b and c and 'first' or
            a and b and not c and 'second' or
            a and not b and c and 'third' or
            a and not b and not c and 'fourth' or
            not a and b and c and 'fifth' or
            not a and b and not c and 'sixth' or
            not a and not b and c and 'seventh' or
            not a and not b and not c and 'eighth')
    

这是可行的,因为 Python 的 ands 和 ors 返回要计算的最后一个值,但您必须知道这一点才能理解这段奇怪的代码。

这三个选项都不是很令人满意。你有什么推荐的?

【问题讨论】:

  • #2 的另一个缺点:当要映射到的值之一是虚假的(例如 0)时,它会失败。
  • +1 实用代码高尔夫 :)
  • 似乎有些人将 'first'、'second' 视为任意占位符,而其他人则将它们视为您需要生成的实际字符串。我很好奇 - 你能解释一下吗?
  • 它们是表示一般情况的占位符。

标签: python if-statement boolean


【解决方案1】:

您可以将 a、b 和 c 视为三个位,当它们放在一起时形成一个 0 到 7 之间的数字。然后,您可以有一个值数组 ['first', 'second', ... 'eighth'] 并将位值用作数组的偏移量。这只是两行代码(一行将位组装成一个 0-7 的值,另一行用于在数组中查找值)。

代码如下:

nth = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first']
nth[(a and 4 or 0) | (b and 2 or 0) | (c and 1 or 0)]

【讨论】:

  • +1 正是我的想法:stackoverflow.com/questions/4726949/… :P 但是我花了一段时间才知道如何在 python 中编写三元运算符。我在 Java 中为这个 FizzBu​​zz 解决方案使用了类似的东西:rosettacode.org/wiki/FizzBuzz#Using_an_array :)
  • 你不能放弃or 0 部分吗?
  • 斯文,大概是这样。我的 Python 有点生锈了。 :-)
  • @Sven 是的,是的,这是可能的:nth[(a and 4)|( b and 2)|(c and 1)] 效果很好。
  • 我相信(a and 4 or 0) 的简洁版本是(4 if a else 0)。它看起来更明显。如果我错了,请纠正我。
【解决方案2】:

使用字典怎么样?

name = {(True, True, True): "first", (True, True, False): "second",
        (True, False, True): "third", (True, False, False): "fourth",
        (False, True, True): "fifth", (False, True, False): "sixth",
        (False, False, True): "seventh", (False, False, False): "eighth"}

print name[a,b,c] # prints "fifth" if a==False, b==True, c==True etc.

【讨论】:

  • +1 正是我在遇到非常相似的事情时所做的。当然有评论# first boolean means x, second means y, third means z。 (最后一行的查找语法错误,应该是print name[(a,b,c)]print name[a,b,c])。
  • @delnan:谢谢,我也注意到了这一点——而且我没有考虑过在元组周围放置括号。很好,没有它们也能工作。元组打包/拆包,嗯?
  • 不,这不是元组解包。元组不需要括号,除非省略它们会引起一些歧义。
  • +1 简单胜于复杂。将这些位编码为 int 可能看起来很聪明,但最终这只是一个不必要的额外步骤。
  • @OzcarRyz 如果它增长,我会考虑在 构建 表时生成密钥,但保持查找结构不变。这确实是自然的数据结构。
【解决方案3】:

也许不会更好,但是怎么样

results = ['first', 'second', 'third', 'fourth', 
           'fifth', 'sixth', 'seventh', 'eighth']
name = results[((not a) << 2) + ((not b) << 1) + (not c)]

【讨论】:

  • @SilentGhost:我总是对运算符优先级感到困惑——添加了缺少的括号:)
  • 非常模糊的代码 imo。我知道它为什么有效,但它太聪明了。 (编辑@Sven:它曾经有一个问题,正如 SilentGhost 所指出的)
  • 不是我的反对意见,但它很难阅读并且非常神奇。如果其他人应该阅读代码,我绝对会建议不要这样做。
  • @Lennart:我也建议不要将其用作一般模式,但实际上我在实际代码中从未遇到过这样的事情:)
  • 奇怪,这对我来说很好。我认为它很聪明,但不是过于聪明或危险的巫术。 +1。
【解决方案4】:

如果 a,b,c 真的是布尔值:

li = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first']
name = li[a*4 + b*2 + c]

如果它们不是布尔值:

li = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first']
a,b,c = map(bool,(a,b,c))
name = li[a*4 + b*2 + c]

克林特·米勒的想法

【讨论】:

    【解决方案5】:

    由于您获得了所有组合,因此您可以根据以下值创建索引:

    def value(a,b,c ): 
       values = ['8th','7th','6th','5th','4th','3rd','2nd','1st']
       index = ( 4 if a else 0 ) + ( 2 if b else 0 ) + ( 1 if c else 0 )
       return values[index]
    
    if __name__ == "__main__":
       print value(True,  True,  True )
       print value(True,  True,  False )
       print value(True,  False, True )
       print value(True,  False, False )
       print value(False, True,  True )
       print value(False, True,  False)
       print value(False, False, True )
       print value(False, False, False)
    

    输出:

    1st
    2nd
    3rd
    4th
    5th
    6th
    7th
    8th
    

    【讨论】:

      【解决方案6】:

      嵌套 if 怎么样 - 这意味着您不必多次检查所有内容并且对我来说更清楚(尽管可能不如其他一些答案那么聪明):

      if a:
          if b:
              if c:
                  name="first"
              else:
                  name="second"
          else:
              if c:
                  name="third"
              else:
                  name="fourth"
      else:
          if b:
              if c:
                  name="fifth"
              else:
                  name="sixth"
          else:
              if c:
                  name="seventh"
              else:
                  name="eighth"
      

      【讨论】:

      • +1:不聪明,但可读性最强。至少你不用摸不着头脑就明白发生了什么。
      • 我没有反对,但我认为这太可怕了。我认为克林特的解决方案加上一个好的评论是最好的
      【解决方案7】:

      另一种选择是创建一个辅助函数:

      def first_true(*args):
          true_vals = (arg for arg in args if arg[0])
          return next(true_vals)[1]
      
      name = first_true((a and b and c, 'first'),
                        (a and b and not c, 'second'),
                        (a and not b and c, 'third'),
                        (a and not b and not c, 'fourth'),
                        (not a and b and c, 'fifth'),
                        (not a and b and not c, 'sixth'),
                        (not a and not b and c, 'seventh'),
                        (not a and not b and not c, 'eighth'))
      

      此方法假定传入的测试之一为真。也可以使用 lambda 使其更惰性。

      【讨论】:

      • 我很抱歉,深夜,没有任何借口,但仍然。
      【解决方案8】:

      测量速度:

      from time import clock
      a,b,c = True,False,False
      
      A,B,C,D,E,F,G,H = [],[],[],[],[],[],[],[]
      
      
      for j in xrange(30):
      
      
          te = clock()
          for i in xrange(10000):
              name = (a and b and c and 'first' or
                      a and b and not c and 'second' or
                      a and not b and c and 'third' or
                      a and not b and not c and 'fourth' or
                      not a and b and c and 'fifth' or
                      not a and b and not c and 'sixth' or
                      not a and not b and c and 'seventh' or
                      not a and not b and not c and 'eighth')
          A.append(clock()-te)
      
      
      
          te = clock()
          for i in xrange(10000):
              if a and b and c:
                  name = 'first'
              elif a and b and not c:
                  name = 'second'
              elif a and not b and c:
                  name = 'third'
              elif a and not b and not c:
                  name = 'fourth'
              elif not a and b and c:
                  name = 'fifth'
              elif not a and b and not c:
                  name = 'sixth'
              elif not a and not b and c:
                  name = 'seventh'
              elif not a and not b and not c:
                  name = 'eighth'
          B.append(clock()-te)
      
          #=====================================================================================
      
          li = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first']
          te = clock()
          for i in xrange(10000):
              name = li[a*4 + b*2 + c]
          C.append(clock()-te)
      
          #=====================================================================================
      
          nth = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first']
          te = clock()
          for i in xrange(10000):
              name = nth[(a and 4 or 0) | (b and 2 or 0) | (c and 1 or 0)]
          D.append(clock()-te)
      
      
          nth = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first']
          te = clock()
          for i in xrange(10000):
              name = nth[(a and 4 or 0) + (b and 2 or 0) + (c and 1 or 0)]
          E.append(clock()-te)
      
          #=====================================================================================
      
          values = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first']
          te = clock()
          for i in xrange(10000):
              name = values[( 4 if a else 0 )| ( 2 if b else 0 ) | ( 1 if c else 0 )]
          F.append(clock()-te)
      
      
          values = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first']
          te = clock()
          for i in xrange(10000):
              name = values[( 4 if a else 0 ) + ( 2 if b else 0 ) + ( 1 if c else 0 )]
          G.append(clock()-te)
      
          #=====================================================================================
      
          dic = {(True, True, True): "first",
                 (True, True, False): "second",
                 (True, False, True): "third",
                 (True, False, False): "fourth",
                 (False, True, True): "fifth",
                 (False, True, False): "sixth",
                 (False, False, True): "seventh",
                 (False, False, False): "eighth"}
          te = clock()
          for i in xrange(10000):
              name = dic[a,b,c]
          H.append(clock()-te)
      
      
      
      
      print min(A),'\n', min(B),'\n\n', min(C),'\n\n', min(D),'\n',min(E),'\n\n',min(F),'\n', min(G),'\n\n', min(H)
      

      结果

      0.0480533140385 
      0.0450973517584 
      
      0.0309056039245 
      
      0.0295291720037 
      0.0286550385594 
      
      0.0280122194301 
      0.0266760160858 
      
      0.0249769174574
      

      【讨论】:

      • 如果对象创建与查询一样频繁(例如:都一次)并且包含在度量中,那么第二个选项(ifs)是迄今为止最快的。
      【解决方案9】:

      我会选择@OscarRyz、@Clint 和@Sven 的列表/位解决方案,但这是另一个:

      
      S1 = frozenset(['first', 'second', 'third', 'fourth'])
      S2 = frozenset(['first', 'second', 'fifth', 'sixth'])
      S3 = frozenset(['first', 'third', 'fifth', 'seventh'])
      last = 'eighth'
      empty = frozenset([])
      
      

      def value(a, b, c): for r in (a and S1 or empty) & (b and S2 or empty) & (c and S3 or empty): return r return last

      【讨论】:

      • 您是否真的测试过任何说字典效率最低的东西?我有并且在我的机器上 tim 的解决方案比 sven 或 clint 的解决方案快得多。
      • 不,这只是一个估计。我认为您是对的,并且 tim 的解决方案更快。当我写那个时,我可能想到了另一个“字典解决方案”。感谢您指出,我将编辑我的答案。
      【解决方案10】:

      如果您的目标是避免编写大量“与”和布尔表达式,您可以使用素数和这样的一个条件(例如 2 个条件)

       cond = (2**cond_1)*(3**cond_2)
      

      所以

      cond == 1 #means cond_1 and cond_2 are False
      cond == 2 #means cond_1 is True and con_2 is False
      cond == 3 #means cond_1 is False and con_2 is True
      cond == 6 #means con_1 and Con_2 are True
      

      这个技巧可以用于使用 3 个素数的 3 个条件,依此类推

      像这样……

      cond = (2**a)*(3**b)*(5**c)
      name = {30:'first', 6: 'second', 10:'third', 2:'fourth',
              15:'fifth', 3:'sixth', 5:'seventh', 1:'eighth'}[cond]
      

      【讨论】:

        【解决方案11】:

        这是一个真值表方法:

        lookup = {'000': 'eighth',
                  '001': 'seventh',
                  '010': 'sixth',
                  '011': 'fifth',
                  '100': 'fourth',
                  '101': 'third',
                  '110': 'second',
                  '111': 'first'}
        
        def key(a, b, c):
            return ''.join([str(a),str(b),str(c)])
        
        name = lookup[key(0,1,1)]
        

        【讨论】:

          猜你喜欢
          • 2012-11-04
          • 2018-08-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多