¿Cómo puedo simplificar las declaraciones repetitivas if-elif en la función de mi sistema de calificación?


20

El objetivo es crear un programa para convertir puntajes de un sistema '0 a 1' a un sistema 'F a A':

  • Si score >= 0.9imprimiera 'A'
  • Si score >= 0.8imprimiría 'B'
  • 0.7, C
  • 0.6, D
  • Y cualquier valor por debajo de ese punto, imprima F

Esta es la forma de construirlo y funciona en el programa, pero es algo repetitivo:

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

Me gustaría saber si hay una manera de construir una función para que las declaraciones compuestas no sean tan repetitivas.

Soy un principiante total, pero haría algo en las líneas de:

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

¿ser posible?

La intención aquí es que luego podamos llamarlo pasando solo el scr, el grado de calificación y la calificación de letra como argumentos:

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

Si fuera posible pasar menos argumentos, sería aún mejor.



Respuestas:


31

Puede usar el módulo bisect para hacer una búsqueda de tabla numérica:

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
Me gustaría tener un +1 adicional para el uso bisect, que considero que se usa muy raramente.
norok2

44
@ norok2 Sin embargo, no creo que una lista de 4 elementos sea el lugar para comenzar. Para listas tan pequeñas, un escaneo lineal probablemente será más rápido. Además, el uso de un argumento predeterminado mutable sin ningún
aviso

1
Claro, pero no duele y dado el aspecto de aprendizaje de la pregunta, me parece bastante apropiado.
norok2

2
Es el ejemplo del módulo bisect
dawg

@schwobaseggl incluso para listas tan pequeñas, bisecar es más rápido. En mi computadora portátil, la solución bisecta toma 1.2 µs y el ciclo toma 1.5 µs
Iftah

10

Puedes hacer algo en este sentido:

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

Esto se usa nextcon un argumento predeterminado en un generador sobre los pares de calificación-calificación creados por zip. Es prácticamente el equivalente exacto de su enfoque de bucle.


5

Puede asignar a cada calificación un valor umbral:

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
Tenga en cuenta que si está utilizando Python 3.6 o inferior, debe hacerlo sorted(grades.items())ya que no se garantiza que los dictados estén ordenados.
wjandrea

Esto no funcionará de manera confiable en todas las versiones de Python. Tenga en cuenta que el orden de un dict no está garantizado. Además, a dictes una estructura de datos innecesariamente pesada, ya que es el orden lo que importa, y de todos modos está buscando por índice (orden), no por clave.
schwobaseggl

1
Claro que no es el más eficiente, pero podría decirse que es el más legible ya que todas las marcas están escritas cerca de su umbral. Prefiero sugerir reemplazar el dict con una tupla de pares.
norok2

@schwobaseggl Para esta tarea específica, sí, una lista de tuplas sería mejor que un dict, pero si todo este código fuera en un módulo, el dict le permitiría buscar el grado de letra -> umbral.
wjandrea

1
@wjandrea En todo caso, necesitarías intercambiar claves y valores para permitir algo así grades[int(score*10)/10.0], pero luego deberías usarlos Decimalcomo flotantes que son notoriamente noticiosas.
schwobaseggl

5

En este caso específico, no necesita módulos externos o generadores. ¡Algunas matemáticas básicas son suficientes (y más rápidas)!

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

Puede usar np.selectde la biblioteca numpy para múltiples condiciones:

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

Tengo una idea simple para resolver esto:

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

Ahora,

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

1

Podrías usar numpy.searchsorted, lo que además te brinda esta buena opción de procesar múltiples puntajes en una sola llamada:

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

Usted proporcionó un caso simple. Sin embargo, si su lógica se está volviendo más complicada, es posible que necesite un motor de reglas para manejar el caos.

Puede probar el motor de reglas de Sauron o encontrar algunos motores de reglas de Python en PYPI.


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

1
Si bien este código puede responder la pregunta, sería mejor incluir algo de contexto, explicando cómo funciona y cuándo usarlo. Las respuestas de solo código no son útiles a largo plazo.
Mustafa

0

También podría usar un enfoque recursivo:

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

Aquí hay algunos enfoques más concisos y menos comprensibles:

La primera solución requiere el uso de la función de piso de la mathbiblioteca.

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

Y si por alguna razón importar la mathbiblioteca te está molestando. Podría usar una solución alternativa para la función de piso:

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

Estos son un poco complicados y recomendaría no usarlos a menos que entiendas lo que está sucediendo. Son soluciones específicas que aprovechan el hecho de que los incrementos en las calificaciones son 0.1, lo que significa que usar un incremento diferente a 0.1 probablemente no funcionaría usando esta técnica. Tampoco tiene una interfaz fácil para asignar marcas a calificaciones. Una solución más general como la de dawg usando bisect es probablemente la solución más limpia o más adecuada de schwobaseggl. No estoy realmente seguro de por qué estoy publicando esta respuesta, pero es solo un intento de resolver el problema sin ninguna biblioteca (no estoy tratando de decir que usar bibliotecas es malo) en una línea que demuestra la naturaleza versátil de Python.


0

Puedes usar un dict.

Código

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

Manifestación

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

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

Si los puntajes están realmente entre 0 y 1, primero multiplique 100, luego busque el puntaje.


0

Espero que lo siguiente pueda ayudar: if 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') más: print ('F')


-3

Podría tener una lista de números, luego una lista de calificaciones para acompañarla:

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

Luego, si desea convertir una puntuación especificada en una calificación de letra, puede hacer esto:

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

Entonces tu puntaje final sería "B".


3
En caso de que se pregunte acerca de los dv: esta solución no proporciona una forma de obtener una calificación similar 0.83a la calificación "B". Tendría que mostrar cómo pasar de la puntuación al índice item.
schwobaseggl
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.