Como posso simplificar declarações repetidas se-elif na função do meu sistema de notas?


20

O objetivo é criar um programa para converter pontuações de um sistema '0 para 1' em um sistema 'F para A':

  • Se score >= 0.9imprimir 'A'
  • Se score >= 0.8imprimir 'B'
  • 0,7, C
  • 0,6, D
  • E qualquer valor abaixo desse ponto, imprima F

Esta é a maneira de construí-lo e funciona no programa, mas é um pouco 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')

Gostaria de saber se existe uma maneira de criar uma função para que as instruções compostas não sejam tão repetitivas.

Sou totalmente iniciante, mas seria algo nas linhas de:

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

seja possível?

A intenção aqui é que, mais tarde, possamos chamá-lo passando apenas scr, numbergrade e letter grade 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')

Se fosse possível passar menos argumentos, seria ainda melhor.



Respostas:


30

Você pode usar o módulo bisect para fazer uma pesquisa de tabela 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
Eu gostaria de ter um +1 adicional para o uso bisect, que acho usado muito raramente.
norok2 4/04

4
@ norok2 Eu não acho que uma lista de 4 elementos seja o lugar para começar. Para listas tão pequenas, uma varredura linear provavelmente será mais rápida. Além do uso de um argumento padrão mutável sem nenhum
aviso

11
Claro, mas não dói e, dado o aspecto de aprendizado da pergunta, acho bastante apropriado.
norok2 4/04

2
É o exemplo do módulo bisect
dawg

@schwobaseggl, mesmo para listas tão pequenas, a divisão é mais rápida. No meu laptop, a solução bisect tem 1,2 µs e o loop 1,5μs
Iftah

10

Você pode fazer algo nesse 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'

Isso é usado nextcom um argumento padrão em um gerador sobre os pares de notas e notas criados por zip. É praticamente o equivalente exato da sua abordagem de loop.


5

Você pode atribuir a cada nota um valor limite:

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
Observe que, se você estiver usando o Python 3.6 ou inferior, deve fazê-lo, sorted(grades.items())já que não é garantido que os dictos sejam classificados.
wjandrea 04/04

Isso não funcionará de maneira confiável em todas as versões do Python. Observe que a ordem de um ditado não é garantida. Também a dicté uma estrutura de dados desnecessariamente pesada, pois é a ordem que importa, e você está pesquisando por índice (ordem) de qualquer maneira, não por chave.
schwobaseggl 04/04

11
Claro que não é o mais eficiente, mas é indiscutivelmente o mais legível, pois todas as marcas são escritas próximas ao seu limite. Prefiro sugerir a substituição do ditado por uma tupla de pares.
norok2 4/04

@schwobaseggl Para esta tarefa específica, sim, uma lista de tuplas seria melhor que um ditado, mas se todo esse código estivesse em um módulo, o ditado permitiria que você pesquisasse a nota da letra -> limite.
wjandrea 04/04

11
@wjandrea Se alguma coisa, você precisaria trocar chaves e valores para permitir algo como grades[int(score*10)/10.0], mas então você deve usar Decimalcomo flutuadores são notoriamente chaves de ditado mal comportadas.
schwobaseggl 04/04

5

Nesse caso específico, você não precisa de módulos ou geradores externos. Alguma matemática básica é suficiente (e mais rápida)!

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

Você pode usar np.selectda biblioteca numpy para várias condições:

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

Eu tenho uma idéia simples para resolver isso:

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

Agora,

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

1

Você pode usar numpy.searchsorted, o que adicionalmente oferece a você essa ótima opção de processar várias pontuações em uma única chamada:

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

Você forneceu um caso simples. No entanto, se sua lógica estiver ficando mais complicada, talvez você precise de um mecanismo de regras para lidar com o caos.

Você pode experimentar o mecanismo Sauron Rule ou encontrar alguns mecanismos de regras Python no PYPI.


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

11
Embora esse código possa responder à pergunta, seria melhor incluir algum contexto, explicando como ele funciona e quando usá-lo. As respostas somente de código não são úteis a longo prazo.
Mustafa

0

Você também pode usar uma abordagem recursiva:

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

Aqui estão algumas abordagens mais sucintas e menos compreensíveis:

A primeira solução requer o uso da função de andar da 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"

E se, por algum motivo, importar a mathbiblioteca está incomodando você. Você pode usar uma solução alternativa para a função de piso:

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

Isso é um pouco complicado e eu não recomendaria usá-los, a menos que você entenda o que está acontecendo. São soluções específicas que tiram vantagem do fato de que os incrementos nas notas são 0,1, o que significa que usar um incremento diferente de 0,1 provavelmente não funcionaria usando essa técnica. Também não possui uma interface fácil para mapear marcas para notas. Uma solução mais geral, como a dawg usando bisect, é provavelmente a mais apropriada ou a solução muito limpa de schwobaseggl. Não sei ao certo por que estou postando esta resposta, mas é apenas uma tentativa de resolver o problema sem bibliotecas (não estou tentando dizer que o uso de bibliotecas é ruim) em uma linha, demonstrando a natureza versátil do python.


0

Você pode usar um ditado.

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

Demo

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

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

Se as pontuações estão realmente entre 0 e 1, multiplique 100 e procure a pontuação.


0

Espero que o seguinte possa ajudar: 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

Você pode ter uma lista de números e uma lista de notas para acompanhar:

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

Então, se você deseja converter uma pontuação especificada em uma nota de uma carta, faça o seguinte:

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

Então sua pontuação final seria "B".


3
Caso você esteja se perguntando sobre os dv's: esta solução não fornece meios de obter uma pontuação como 0.83a nota "B". Você precisaria mostrar como passar da pontuação para o índice item.
schwobaseggl 04/04
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.