Comment puis-je simplifier les instructions if-elif répétitives dans la fonction de mon système de notation?


20

L'objectif est de construire un programme pour convertir les scores d'un système «0 à 1» en un système «F à A»:

  • Si score >= 0.9imprimerait 'A'
  • Si score >= 0.8imprimerait «B»
  • 0,7, C
  • 0,6, D
  • Et toute valeur en dessous de ce point, imprimez F

C'est la façon de le construire et cela fonctionne sur le programme, mais c'est un peu répétitif:

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

Je voudrais savoir s'il existe un moyen de construire une fonction afin que les instructions composées ne soient pas aussi répétitives.

Je suis un débutant total, mais je dirais quelque chose comme:

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

être possible?

L'intention ici est que plus tard nous pouvons l'appeler en ne passant que les arguments scr, numbergrade et letter grade comme arguments:

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

S'il était possible de passer moins d'arguments, ce serait encore mieux.



2
Est-ce que cela répond à votre question? Comment créer un système de classement en python?
RoadRunner

Réponses:


31

Vous pouvez utiliser le module bissect pour effectuer une recherche de table numérique:

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
Je voudrais avoir un +1 supplémentaire pour l'utilisation bisect, que je trouve trop rarement utilisé.
norok2

4
@ norok2 Je ne pense pas qu'une liste de 4 éléments soit le point de départ. Pour ces petites listes, un balayage linéaire sera probablement plus rapide. Plus l'utilisation d'un argument par défaut mutable sans aucun heads-up;)
schwobaseggl

1
Bien sûr, mais cela ne fait pas de mal et compte tenu de l'aspect d'apprentissage de la question, je trouve cela tout à fait approprié.
norok2

2
C'est l'exemple du module bissect
dawg

@schwobaseggl, même pour ces petites listes, la bissectrice est plus rapide. Sur mon ordinateur portable, la solution bissectrice prend 1,2 µs et la boucle prend 1,5 µs
Iftah

10

Vous pouvez faire quelque chose dans ce sens:

# 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'

Cela utilise nextavec un argument par défaut sur un générateur sur les paires de notes créées par zip. C'est pratiquement l'équivalent exact de votre approche en boucle.


5

Vous pouvez attribuer à chaque note une valeur seuil:

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
Remarque, si vous utilisez Python 3.6 ou une version sorted(grades.items())antérieure , vous devriez le faire car les dict ne sont pas garantis pour être triés.
wjandrea

Cela ne fonctionnera pas de manière fiable dans toutes les versions de Python. Notez que l'ordre d'un dict n'est pas garanti. De plus, a dictest une structure de données inutilement lourde, car c'est l'ordre qui compte, et vous recherchez de toute façon par index (ordre), pas par clé.
schwobaseggl

1
Bien sûr, ce n'est pas le plus efficace, mais c'est sans doute le plus lisible car toutes les marques sont écrites près de leur seuil. Je préférerais plutôt remplacer le dict par un tuple de paires.
norok2

@schwobaseggl Pour cette tâche spécifique, oui, une liste de tuples serait mieux qu'un dict, mais si tout ce code allait dans un module, le dict vous permettrait de rechercher le grade de la lettre -> seuil.
wjandrea

1
@wjandrea Si quoi que ce soit, vous auriez besoin d'échanger des clés et des valeurs pour permettre quelque chose comme grades[int(score*10)/10.0], mais alors vous devriez les utiliser Decimalcar les flotteurs sont des clés dict notoirement mal comportées.
schwobaseggl

5

Dans ce cas précis, vous n'avez pas besoin de modules ou de générateurs externes. Un peu de mathématiques de base est suffisant (et plus rapide)!

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

Vous pouvez utiliser à np.selectpartir de la bibliothèque numpy pour plusieurs conditions:

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

J'ai une idée simple pour résoudre ce problème:

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

Maintenant,

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

1

Vous pouvez utiliser numpy.searchsorted, ce qui vous offre également cette belle option de traitement de plusieurs partitions en un seul appel:

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

Vous avez fourni un cas simple. Cependant, si votre logique devient plus compliquée, vous devrez peut-être moteur de règles pour gérer le chaos.

Vous pouvez essayer le moteur de règles Sauron ou trouver des moteurs de règles Python à partir de PYPI.


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

1
Bien que ce code puisse répondre à la question, il serait préférable d'inclure un certain contexte, expliquant comment il fonctionne et quand l'utiliser. Les réponses uniquement codées ne sont pas utiles à long terme.
Mustafa

0

Vous pouvez également utiliser une approche récursive:

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

Voici quelques approches plus succinctes et moins compréhensibles:

La première solution nécessite l'utilisation de la fonction étage de la mathbibliothèque.

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

Et si pour une raison quelconque, l'importation de la mathbibliothèque vous dérange. Vous pouvez utiliser une solution de contournement pour la fonction étage:

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

Ce sont un peu compliqués et je vous déconseille de les utiliser à moins que vous ne compreniez ce qui se passe. Ce sont des solutions spécifiques qui profitent du fait que les incréments de notes sont de 0,1, ce qui signifie que l'utilisation d'un incrément autre que 0,1 ne fonctionnerait probablement pas avec cette technique. Il n'a pas non plus d'interface simple pour mapper les marques aux notes. Une solution plus générale telle que celle de dawg utilisant bisect est probablement plus appropriée ou la solution très propre de schwobaseggl. Je ne sais pas vraiment pourquoi je poste cette réponse, mais c'est juste une tentative de résoudre le problème sans aucune bibliothèque (je n'essaye pas de dire que l'utilisation des bibliothèques est mauvaise) en une seule ligne démontrant la nature polyvalente de python.


0

Vous pouvez utiliser un dict.

Code

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

Démo

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

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

Si les scores sont réellement compris entre 0 et 1, multipliez d'abord 100, puis recherchez le score.


0

J'espère que ce qui suit pourrait aider: si 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

Vous pourriez avoir une liste de nombres, puis une liste de notes pour aller avec:

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

Ensuite, si vous souhaitez convertir un score spécifié en note de lettre, vous pouvez le faire:

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

Votre score final serait alors "B".


3
Au cas où vous vous poseriez des questions sur les DV: cette solution ne fournit aucun moyen de passer d'un score comme 0.83à la note "B". Il faudrait montrer comment passer du score à l'index item.
schwobaseggl
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.