Modul eficient Python de scriere a cazului switch cu comparație (Programare, Python, Declarație De Comutare)

Kenny a intrebat.

În mod normal, implementez Switch/Case pentru comparație egală folosind Dictionary.

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

în loc de

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

Există o strategie pentru a implementa o abordare similară pentru comparație non-egală ?

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'

Știu că poate fi dificil cu <, <=, >, >=, dar există vreo strategie pentru a generaliza asta sau pentru a genera declarații automate din, să zicem, o listă

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

și un indicator care să spună dacă este < sau <=

Comentarii

  • Cu siguranță că trebuie să avem deja un duplicat pentru acest tip de întrebări. Cineva? –  > Por Aran-Fey.
  • Python nu are o instrucțiune switch. Folosește doar if,elif,else –  > Por juanpa.arrivillaga.
  • Argh, bineînțeles că această întrebare de aici este și ea nepotrivită pentru a fi o canonică bună… doar că a avut să ceară o modalitate de a deosebi < și <=… –  > Por Aran-Fey.
5 răspunsuri
Alain T.

Un dicționar poate conține o mulțime de valori, dacă intervalele dvs. nu sunt prea largi, ați putea face un dicționar similar cu cel pe care l-ați avut pentru condițiile de egalitate prin extinderea fiecărui interval în mod programatic:

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'

De asemenea, ați putea elimina pur și simplu condițiile redundante din declarația if/elif și să o formatați puțin diferit. Aceasta ar arăta aproape ca o declarație case:

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

pentru a avea un scor repetat < ați putea defini o funcție case și să o utilizați cu valoarea:

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'

Ați putea generaliza acest lucru prin crearea unei funcții switch care returnează o funcție „case” care se aplică valorii de test cu un model de comparație generic:

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

Această versiune generică permite tot felul de combinații:

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'

Și mai există încă o altă modalitate, folosind o funcție lambda atunci când tot ce trebuie să faceți este să returnați o valoare:

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"

Această ultimă variantă poate fi, de asemenea, exprimată cu ajutorul unei înțelegeri de listă și a unui tabel de corespondență:

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"

[EDIT] mai exact la întrebare, o soluție generalizată poate fi creată folosind o funcție de configurare care returnează o funcție de cartografiere în conformitate cu parametrii dumneavoastră:

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

rețineți că, cu puțin mai multă muncă, puteți îmbunătăți performanța funcției returnate folosind modulul bisect

Olivier Melançon

bisect poate fi utilizat pentru o astfel de problemă de clasificare. În special, documentația oferă o exemplu care rezolvă o problemă foarte asemănătoare cu a dumneavoastră.

Iată același exemplu adaptat la cazul dumneavoastră de utilizare. Funcția returnează două valori: nota literară și un bool steag care indică dacă potrivirea a fost exactă.

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

În exemplul de mai sus, am presupus că ultimul dvs. punct de întrerupere a fost 100 pentru E. Dacă într-adevăr nu doriți o limită superioară, observați că puteți înlocui 100 cu math.inf pentru a păstra codul funcțional.

blhsing

Pentru cazul dvs. particular, o abordare eficientă pentru a converti un punctaj într-o notă în O(1) ar fi să folosiți 100 minus nota împărțită la 10 ca indice de șir pentru a obține nota literală:

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

astfel încât:

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))

ieșiri:

E
E
D
C
B
A
A

Prune

Da, există o strategie, dar nu la fel de curată ca modelele de gândire umane. Mai întâi câteva note:

  • Există și alte întrebări care se ocupă de „Python switch”; voi presupune că le-ați consultat deja și ați eliminat aceste soluții din calcul.
  • Structura pe care ați postat-o este nu a list; este o încercare invalidă de a fi un dict. Cheile trebuie să fie hashabile; listele pe care le furnizați nu sunt chei valide.
  • Aveți două tipuri separate de comparație aici: potrivire exactă la limita inferioară și limita de cuprindere a intervalului.

Acestea fiind spuse, voi păstra conceptul de tabel de căutare, dar îl vom reduce la un numitor comun scăzut pentru a-l face mai ușor de înțeles și de modificat pentru alte considerente.

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

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

cat = grade[idx]

exact este indicatorul pe care l-ați solicitat.

VladimirJosephStephanOrlovsky
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))