Come posso semplificare le ripetitive istruzioni if-elif nella funzione del mio sistema di classificazione?


20

L'obiettivo è costruire un programma per convertire i punteggi da un sistema '0 a 1' in un sistema 'F in A':

  • Se score >= 0.9stampasse 'A'
  • Se score >= 0.8stampasse 'B'
  • 0.7, C
  • 0.6, D
  • E qualsiasi valore al di sotto di quel punto, stampa F

Questo è il modo di costruirlo e funziona sul programma, ma è in qualche modo ripetitivo:

if scr >= 0.9:
    print('A')
elif scr >= 0.8:
    print('B')
elif scr >= 0.7:
    print('C')
elif scr >= 0.6:
    print('D')
else:
    print('F')

Vorrei sapere se esiste un modo per creare una funzione in modo che le istruzioni composte non siano così ripetitive.

Sono un principiante assoluto, ma farei qualcosa del genere:

def convertgrade(scr, numgrd, ltrgrd):
    if scr >= numgrd:
        return ltrgrd
    if scr < numgrd:
        return ltrgrd

Essere possibile?

L'intenzione qui è che in seguito possiamo chiamarlo solo passando scr, scrutinio numerico e voto in lettere come argomenti:

convertgrade(scr, 0.9, 'A')
convertgrade(scr, 0.8, 'B')
convertgrade(scr, 0.7, 'C')
convertgrade(scr, 0.6, 'D')
convertgrade(scr, 0.6, 'F')

Se fosse possibile passare meno argomenti, sarebbe ancora meglio.



Risposte:


31

È possibile utilizzare il modulo bisect per eseguire una ricerca di una tabella numerica:

from bisect import bisect 

def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
     i = bisect(breakpoints, score)
     return grades[i]

>>> [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
['F', 'A', 'C', 'C', 'B', 'A', 'A']

2
Vorrei avere un +1 aggiuntivo per l'uso bisect, che trovo usato troppo raramente.
norok2

4
@ norok2 Non credo che un elenco di 4 elementi sia il punto di partenza. Per elenchi così piccoli una scansione lineare sarà probabilmente più veloce. Inoltre l'uso di un argomento predefinito mutevole senza alcun
avvertimento

1
Certo, ma non fa male e dato l'aspetto di apprendimento della domanda, lo trovo abbastanza appropriato.
norok2

2
È l'esempio del modulo bisect
dawg

@schwobaseggl anche per liste così piccole bisect è più veloce. Sul mio laptop la soluzione bisect richiede 1,2µs e il ciclo richiede 1,5µs
Iftah il

10

Puoi fare qualcosa in questo modo:

# if used repeatedly, it's better to declare outside of function and reuse
# grades = list(zip('ABCD', (.9, .8, .7, .6)))

def grade(score):
    grades = zip('ABCD', (.9, .8, .7, .6))
    return next((grade for grade, limit in grades if score >= limit), 'F')

>>> grade(1)
'A'
>>> grade(0.85)
'B'
>>> grade(0.55)
'F'

Questo utilizza nextun argomento predefinito su un generatore sulle coppie di punteggio create da zip. È praticamente l'equivalente esatto del tuo approccio loop.


5

È possibile assegnare a ciascun voto un valore di soglia:

grades = {"A": 0.9, "B": 0.8, "C": 0.7, "D": 0.6, "E": 0.5}

def convert_grade(scr):
    for ltrgrd, numgrd in grades.items():
        if scr >= numgrd:
            return ltrgrd
    return "F"

2
Nota, se stai usando Python 3.6 o sorted(grades.items())versioni precedenti , dovresti farlo poiché i dadi non sono garantiti per essere ordinati.
wjandrea,

Questo non funzionerà in modo affidabile in tutte le versioni di Python. Si noti che l'ordine di un dict non è garantito. Inoltre a dictè una struttura di dati inutilmente pesante, poiché è l'ordine che conta e stai comunque cercando in base all'indice (ordine), non alla chiave.
schwobaseggl

1
Certo non è il più efficiente, ma è probabilmente il più leggibile poiché tutti i segni sono scritti vicino alla loro soglia. Preferirei suggerire di sostituire il dict con una tupla di coppie.
norok2

@schwobaseggl Per questo compito specifico, sì, un elenco di tuple sarebbe meglio di un dict, ma se tutto questo codice stesse andando in un modulo, il dict ti permetterebbe di cercare il grado della lettera -> soglia.
wjandrea,

1
@wjandrea Se non altro, dovresti scambiare chiavi e valori per consentire qualcosa del genere grades[int(score*10)/10.0], ma poi dovresti usare Decimalcome float sono notoriamente maliziose chiavi di dict.
schwobaseggl

5

In questo caso specifico non sono necessari moduli o generatori esterni. Un po 'di matematica di base è abbastanza (e più veloce)!

grades = ["A", "B", "C", "D", "F"]

def convert_score(score):
    return grades[-max(int(score * 10) - 5, 0) - 1]

# Examples:
print(convert_grade(0.61)) # "D"
print(convert_grade(0.37)) # "F"
print(convert_grade(0.94)) # "A"

2

È possibile utilizzare np.selectdalla libreria numpy per più condizioni:

>> x = np.array([0.9,0.8,0.7,0.6,0.5])

>> conditions  = [ x >= 0.9,  x >= 0.8, x >= 0.7, x >= 0.6]
>> choices     = ['A','B','C','D']

>> np.select(conditions, choices, default='F')
>> array(['A', 'B', 'C', 'D', 'F'], dtype='<U1')

2

Ho una semplice idea per risolvere questo:

def convert_grade(numgrd):
    number = min(9, int(numgrd * 10))
    number = number if number >= 6 else 4
    return chr(74 - number)

Adesso,

print(convert_grade(.95))  # --> A 
print(convert_grade(.9))  # --> A
print(convert_grade(.4))  # --> F
print(convert_grade(.2))  # --> F

1

È possibile utilizzare numpy.searchsorted, che offre inoltre questa piacevole opzione di elaborazione di più punteggi in una singola chiamata:

import numpy as np

grades = np.array(['F', 'D', 'C', 'B', 'A'])
thresholds = np.arange(0.6, 1, 0.1)

scores = np.array([0.75, 0.83, 0.34, 0.9])
grades[np.searchsorted(thresholds, scores)]  # output: ['C', 'B', 'F', 'A']

1

Hai fornito un caso semplice. Tuttavia, se la tua logica sta diventando più complicata, potresti aver bisogno di un motore di regole per gestire il caos.

Puoi provare il motore Sauron Rule o trovare alcuni motori delle regole Python da PYPI.


1
>>> grade = lambda score:'FFFFFFDCBAA'[int(score*100)//10]
>>> grade(0.8)
'B'

1
Sebbene questo codice possa rispondere alla domanda, sarebbe meglio includere un certo contesto, spiegando come funziona e quando usarlo. Le risposte di solo codice non sono utili a lungo termine.
Mustafa,

0

È inoltre possibile utilizzare un approccio ricorsivo:

grade_mapping = list(zip((0.9, 0.8, 0.7, 0.6, 0), 'ABCDF'))
def get_grade(score, index = 0):
    if score >= grade_mapping[index][0]:
        return(grade_mapping[index][1])
    else:
        return(get_grade(score, index = index + 1))

>>> print([get_grade(score) for score in [0, 0.59, 0.6, 0.69, 0.79, 0.89, 0.9, 1]])
['F', 'F', 'D', 'D', 'C', 'B', 'A', 'A']

0

Ecco alcuni approcci più concisi e meno comprensibili:

La prima soluzione richiede l'uso della funzione floor dalla mathlibreria.

from math import floor
def grade(mark):
    return ["D", "C", "B", "A"][min(floor(10 * mark - 6), 3)] if mark >= 0.6 else "F"

E se per qualche motivo importare la mathlibreria ti dà fastidio. È possibile utilizzare una soluzione alternativa per la funzione floor:

def grade(mark):
    return ["D", "C", "B", "A"][min(int(10 * mark - 6) // 1, 3)] if mark >= 0.6 else "F"

Questi sono un po 'complicati e consiglierei di non usarli a meno che tu non capisca cosa sta succedendo. Sono soluzioni specifiche che sfruttano il fatto che gli incrementi in gradi sono 0,1, il che significa che l'utilizzo di un incremento diverso da 0,1 probabilmente non funzionerebbe con questa tecnica. Inoltre, non ha un'interfaccia semplice per mappare i voti in gradi. Una soluzione più generale come quella di dawg che usa bisect è probabilmente più appropriata o la soluzione molto pulita di schwobaseggl. Non sono davvero sicuro del perché sto pubblicando questa risposta, ma è solo un tentativo di risolvere il problema senza librerie (non sto cercando di dire che usare le librerie è male) in una riga dimostrando la natura versatile di Python.


0

Puoi usare un dict.

Codice

def grade(score):
    """Return a letter grade."""
    grades = {100: "A", 90: "A", 80: "B", 70: "C", 60: "D"}
    return grades.get((score // 10) * 10, "F")

dimostrazione

[grade(scr) for scr in [100, 33, 95, 61, 77, 90, 89]]

# ['A', 'F', 'A', 'D', 'C', 'A', 'B']

Se i punteggi sono effettivamente compresi tra 0 e 1, prima moltiplica 100, quindi cerca il punteggio.


0

Spero che ciò che segue potrebbe essere d'aiuto: se scr> = 0.9: print ('A') elif 0.9> scr> = 0.8: print ('B') elif 0.8> scr> = 0.7: Print ('C') elif 0.7 scr> = 0.6: print ( 'D') else: print ( 'F')


-3

Potresti avere un elenco di numeri, quindi un elenco di voti da seguire:

scores = (0.9, 0.8, 0.7, 0.6, 0.6)
lettergrades = ("A", "B", "C", "D", "F", "F")

Quindi, se desideri convertire un punteggio specificato in un voto in lettere, puoi farlo:

item = 1 # Item 1 would be 0.8
scr = lettergrades[item]

Quindi il tuo punteggio finale sarebbe "B".


3
Nel caso ti stia chiedendo dei dv: questa soluzione non fornisce alcun modo per ottenere da un punteggio simile 0.83al voto "B". Dovresti mostrare come andare dal punteggio all'indice item.
schwobaseggl
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.