【问题标题】:Python efficient way of writing switch case with comparisonPython通过比较编写switch case的有效方法
【发布时间】:2019-08-13 23:55:42
【问题描述】:

我通常使用字典实现 switch/case 以进行相等比较。

dict = {0:'zero', 1:'one', 2:'two'};
a=1; res = dict[a]

而不是

if a == 0:
  res = 'zero'
elif a == 1:
  res = 'one'
elif a == 2:
  res = 'two'

是否有策略来实施类似的非相等比较方法?

if score <= 10:
  cat = 'A'
elif score > 10 and score <= 30:
  cat = 'B'
elif score > 30 and score <= 50:
  cat = 'C'
elif score > 50 and score <= 90:
  cat = 'D'
else:
  cat = 'E'

我知道使用 、>= 可能会很棘手,但是有什么策略可以概括它或从我们说的列表中生成自动语句

{[10]:'A', [10,30]:'B', [30,50]:'C',[50,90]:'D',[90]:'E'}

还有一些标志表明它是&lt; 还是&lt;=

【问题讨论】:

  • 现在我们肯定必须为这类问题准备一份副本吗?有人吗?
  • Python 没有 switch 语句。只需使用 if,elif,else
  • 啊,当然,这里的这个问题也不适合成为一个很好的规范……它只是 不得不 要求一种方法来区分
  • 相关:Replacements for switch statement in Python? [原文如此]。它还具有 Python 3.10 (2021) 引入的 switch 语句。

标签: python switch-statement


【解决方案1】:

字典可以包含很多值。如果您的范围不是太宽,您可以通过以编程方式扩展每个范围来制作一个与相等条件相似的字典:

from collections import defaultdict

ranges   = {(0,10):'A', (10,30):'B', (30,50):'C',(50,90):'D'}
valueMap = defaultdict(lambda:'E')
for r,letter in ranges.items():
    valueMap.update({ v:letter for v in range(*r) })

valueMap[701] # 'E'
valueMap[7] # 'A'

您也可以从 if/elif 语句中删除多余的条件,并将其格式稍有不同。这几乎看起来像一个案例陈述:

if   score < 10 : cat = 'A'
elif score < 30 : cat = 'B'
elif score < 50 : cat = 'C'
elif score < 90 : cat = 'D'
else            : cat = 'E'

为避免重复score &lt;,您可以定义一个案例函数并将其与值一起使用:

score = 43
case = lambda x: score < x
if   case(10): cat = "A"
elif case(30): cat = "B"
elif case(50): cat = "C"
elif case(90): cat = "D"
else         : cat = "E"
print (cat) # 'C'

您可以通过创建一个 switch 函数来概括这一点,该函数返回一个“case”函数,该函数适用于具有通用比较模式的测试值:

def switch(value):
    def case(check,lessThan=None):
        if lessThan is not None:
            return (check is None or check <= value) and value < lessThan
        if type(value) == type(check): return value == check
        if isinstance(value,type(case)): return check(value)
        return value in check
    return case

这个通用版本允许各种组合:

score = 35
case = switch(score)
if   case(0,10)         : cat = "A"
elif case([10,11,12,13,14,15,16,17,18,19]):
                          cat = "B"
elif score < 30         : cat = "B"
elif case(30) \
  or case(range(31,50)) : cat = 'C'
elif case(50,90)        : cat = 'D'
else                    : cat = "E"
print(cat) # 'C'

当您需要做的就是返回一个值时,还有另一种使用 lambda 函数的方法:

score = 41
case  = lambda x,v: v if score<x else None
cat   = case(10,'A') or case(20,'B') or case(30,'C') or case(50,'D') or 'E'
print(cat) # "D"

最后一个也可以使用列表推导和映射表来表示:

mapping = [(10,'A'),(30,'B'),(50,'C'),(90,'D')]
scoreCat = lambda s: next( (L for x,L in mapping if s<x),"E" )

score = 37
cat = scoreCat(score)
print(cat) #"D"

更具体地说,可以使用设置函数创建通用解决方案,该函数根据您的参数返回映射函数:

def rangeMap(*breaks,inclusive=False):
    default = breaks[-1] if len(breaks)&1 else None
    breaks  = list(zip(breaks[::2],breaks[1::2]))
    def mapValueLT(value):
        return next( (tag for tag,bound in breaks if value<bound), default)
    def mapValueLE(value):
        return next( (tag for tag,bound in breaks if value<=bound), default)
    return mapValueLE if inclusive else mapValueLT

scoreToCategory = rangeMap('A',10,'B',30,'C',50,'D',90,'E')

print(scoreToCategory(53)) # D
print(scoreToCategory(30)) # C

scoreToCategoryLE = rangeMap('A',10,'B',30,'C',50,'D',90,'E',inclusive=True)

print(scoreToCategoryLE(30)) # B

请注意,您可以使用 bisect 模块提高返回函数的性能。

【讨论】:

    【解决方案2】:

    bisect 模块可用于此类分类问题。特别是,该文档提供了一个example,它解决了与您的问题非常相似的问题。

    这是适用于您的用例的相同示例。该函数返回两个值:字母等级和bool 标志,指示匹配是否准确。

    from bisect import bisect_left
    
    grades = "ABCDE"
    breakpoints = [10, 30, 50, 90, 100]
    
    def grade(score):
              index = bisect_left(breakpoints, score)
              exact = score == breakpoints[index]
              grade = grades[index]
              return grade, exact
    
    grade(10) # 'A', True
    grade(15) # 'B', False
    

    在上面,我假设您的最后一个断点是 100 用于 E。如果您确实不想要上限,请注意您可以将 100 替换为 math.inf 以保持代码正常工作。

    【讨论】:

      【解决方案3】:

      对于您的特定情况,以 O(1) 时间复杂度将分数转换为等级的有效方法是使用 100 减去分数除以 10 作为字符串索引来获得字母等级:

      def get_grade(score):
          return 'EDDDDCCBBAA'[(100 - score) // 10]
      

      这样:

      print(get_grade(100))
      print(get_grade(91))
      print(get_grade(90))
      print(get_grade(50))
      print(get_grade(30))
      print(get_grade(10))
      print(get_grade(0))
      

      输出:

      E
      E
      D
      C
      B
      A
      A
      

      【讨论】:

        【解决方案4】:

        Python 3.10 引入了match case(基本上是switch),你可以把它当作

        def check_number(no):
          match no:
            case 0:
              return 'zero
            case 1:
              return 'one'
            case 2:
              return 'two'
            case _:
              return "Invalid num"

        这是我尝试的示例。

        【讨论】:

          【解决方案5】:

          是的,有一种策略,但不如人类的思维模式那么清晰。首先是一些注意事项:

          • 还有其他关于“Python switch”的问题;我假设您已经咨询过他们并排除了这些解决方案。
          • 您发布的结构不是list;这是对dict 的无效尝试。密钥必须是可散列的;您提供的列表不是有效的键。
          • 这里有两种不同类型的比较:与下限完全匹配和范围包含。

          也就是说,我将保留查找表的概念,但我们将把它放到一个较低的公分母上,以便于理解和更改以用于其他考虑。

          low = [10, 30, 50, 90]
          grade = "ABCDE"
          
          for idx, bkpt in enumerate(low):
              if score <= bkpt:
                  exact = (score == bkpt)
                  break
          
          cat = grade[idx]
          

          exact 是您请求的标志。

          【讨论】:

            【解决方案6】:

            Python(在 python 3.10 中添加,帽子提示 Peter Mortensen)没有 case 语句。只需写一长串elifs

            if score <= 10:
              return 'A'
            if score <= 30:
              return 'B'
            if score <= 50:
              return 'C'
            if score <= 90:
              return 'D'
            return 'E'
            

            诸如查字典之类的东西听起来不错,但实际上,它太慢了。 elifs 打败了他们。

            【讨论】:

            【解决方案7】:
            low = [10,30,50,70,90]
            gradE = "FEDCBA"
            
            def grade(score):
                for i,b in enumerate(low):
                    #if score < b:   # 0--9F,10-29E,30-49D,50-69C,70-89B,90-100A Easy
                    if score <= b:   # 0-10F,11-30E,31-50D,51-70C,71-90B,91-100A Taff
                        return gradE[i]
                else:return gradE[-1]
            
            for score in range(0,101):
                print(score,grade(score))
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-06-16
              • 2020-03-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-12-12
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多