Python эффективный способ написания регистра переключателей со сравнением - PullRequest
1 голос
/ 23 марта 2019

Обычно я использую 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'}

и какой-нибудь флаг, чтобы сказать, является ли это <или <= </p>

Ответы [ 5 ]

1 голос
/ 23 марта 2019

Словарь может содержать много значений, если ваши диапазоны не слишком широки, вы можете создать словарь, аналогичный тому, который вы использовали для условий равенства, расширяя каждый диапазон программно:

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[0],r[1]) })

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

Вы также можете просто удалить из себя лишние условия, если / elif, и отформатировать его немного по-другому. Это выглядело бы почти как заявление случая:

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

чтобы избежать повторения счета <вы можете определить функцию регистра и использовать ее со значением: </p>

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'

И есть еще один способ использования лямбда-функции, когда все, что вам нужно сделать, это вернуть значение:

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"
1 голос
/ 23 марта 2019

Да, есть стратегия, но не такая чистая, как у людей. Сначала некоторые заметки:

  • Есть и другие вопросы, связанные с «переключателем Python»; Я предполагаю, что вы уже консультировались с ними и исключили эти решения из рассмотрения.
  • Структура, которую вы разместили: не a list; это недопустимая попытка dict. Ключи должны быть хэшируемыми; указанные вами списки не являются действительными ключами.
  • У вас есть два отдельных типа сравнения: точное соответствие с нижней границей и ограничение диапазона.

Тем не менее, я сохраню концепцию справочной таблицы, но мы опустим ее до низкого общего знаменателя, чтобы его было легко понять и изменить для других соображений.

low = [10, 30, 50, 90]
grade = "ABCDE"

for idx, bkpt in enumerate(low):
    if score <= low:
        exact = (score == low)
        break

cat = grade[idx]

exact - флаг, который вы запрашивали.

1 голос
/ 23 марта 2019

В вашем конкретном случае эффективный подход для преобразования оценки в оценку за 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
1 голос
/ 23 марта 2019

Модуль bisect может использоваться для такой проблемы классификации.В частности, документация предлагает пример , который решает проблему, очень похожую на вашу.

Вот тот же пример, адаптированный для вашего варианта использования.Функция возвращает два значения: буквенная оценка и флаг 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, чтобы код работал.

0 голосов
/ 23 марта 2019
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))
...