Python-efficiënte manier om switch-case te schrijven met vergelijking

Normaal gesproken implementeer ik Switch/Case voor gelijke vergelijking met Dictionary.

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

in plaats van

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

Is er een strategie om een vergelijkbare aanpak voor ongelijke vergelijking te implementeren?

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'

Ik weet dat dat lastig kan zijn met de <, <=, >, >=, maar is er een strategie om dat te generaliseren of om automatische statements te genereren van laten we zeggen een lijst

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

en een vlag om te zeggen of het < of <=


Antwoord 1, autoriteit 100%

Een woordenboek kan veel waarden bevatten. Als uw bereik niet te breed is, kunt u een woordenboek maken dat lijkt op het woordenboek dat u had voor de gelijkheidsvoorwaarden door elk bereik programmatisch uit te breiden:

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'

Je kunt ook gewoon de overbodige voorwaarden uit je if/elif-statement verwijderen en het een beetje anders opmaken. Dat zou er bijna uitzien als een case-statement:

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

om herhaling van de partituur te voorkomen < je zou een case-functie kunnen definiëren en deze gebruiken met de waarde:

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'

Je zou dit kunnen veralgemenen door een schakelfunctie te maken die een “case”-functie retourneert die van toepassing is op de testwaarde met een generiek vergelijkingspatroon:

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

Deze generieke versie maakt allerlei combinaties mogelijk:

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'

En er is nog een andere manier om een lambda-functie te gebruiken als je alleen maar een waarde hoeft terug te geven:

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"

Dit laatste kan ook worden uitgedrukt met behulp van een lijstbegrip en een toewijzingstabel:

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] meer specifiek op de vraag, een algemene oplossing kan worden gemaakt met behulp van een setup-functie die een mapping-functie retourneert in overeenstemming met uw parameters:

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

merk op dat u met wat meer werk de prestaties van de geretourneerde functie kunt verbeteren met behulp van de bisect-module


Antwoord 2, autoriteit 60%

De bisect-module kan worden gebruikt voor een dergelijk categoriseringsprobleem. In het bijzonder biedt de documentatie een voorbeelddat een probleem oplost dat erg lijkt op naar de jouwe.

Hier is hetzelfde voorbeeld aangepast aan uw gebruiksscenario. De functie retourneert twee waarden: de letter grade en een boolvlag die aangeeft of de overeenkomst exact was.

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

In het bovenstaande ging ik ervan uit dat uw laatste onderbrekingspunt 100was voor E. Als je echt geen bovengrens wilt, merk dan op dat je 100kunt vervangen door math.infom de code te laten werken.


Antwoord 3, autoriteit 40%

Voor uw specifieke geval zou een efficiënte benadering om een score om te zetten in een cijfer in O(1)tijdcomplexiteit zijn om 100 minus de score gedeeld door 10 te gebruiken als een tekenreeksindex om de letter te verkrijgen cijfer:

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

zodat:

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

uitgangen:

E
E
D
C
B
A
A

Antwoord 4, autoriteit 40%

Python 3.10 introduceerde matchcase (in feite switch) en je kunt het gebruiken als

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

Antwoord 5, autoriteit 20%

Ja, er is een strategie, maar niet zo zuiver als menselijke denkpatronen. Eerst wat opmerkingen:

  • Er zijn andere vragen die te maken hebben met “Python-switch”; Ik neem aan dat je ze al hebt geraadpleegd en die oplossingen buiten beschouwing hebt gelaten.
  • De structuur die je hebt gepost is geeneen list; het is een ongeldige poging tot een dict. Sleutels moeten hashbaar zijn; de lijsten die je geeft zijn geen geldige sleutels.
  • Je hebt hier twee verschillende soorten vergelijkingen: exacte overeenkomst met de ondergrens en bereikbeperking.

Dat gezegd hebbende, zal ik het concept van een opzoektabel behouden, maar we zullen het laten vallen op een kleine gemene deler om het gemakkelijk te begrijpen en te wijzigen voor andere overwegingen.

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

exactis de vlag die je hebt aangevraagd.


Antwoord 6

Python heeft geen case-statement, schrijf gewoon lange reeksen elifs.

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

dingen zoals het opzoeken in een woordenboek klinkt geweldig, maar het is echt, het is te traag, elifs verslaan ze allemaal.


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

Other episodes