Jak mogę uprościć powtarzające się instrukcje if-elif w mojej funkcji oceniania?

19

Celem jest zbudowanie programu do konwersji wyników z systemu „0 na 1” na system „F na A”:

  • Gdyby score >= 0.9wydrukował „A”
  • Gdyby score >= 0.8wydrukował „B”
  • 0,7, C
  • 0,6, D
  • I każdą wartość poniżej tego punktu, wydrukuj F.

Jest to sposób na zbudowanie go i działa w programie, ale jest nieco powtarzalny:

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

Chciałbym wiedzieć, czy istnieje sposób na zbudowanie funkcji, aby instrukcje złożone nie były tak powtarzalne.

Jestem całkowicie początkującym, ale chciałbym coś w stylu:

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

dać?

Chodzi o to, że później możemy to nazwać, przekazując argumenty scr, numergrade i ocenę liter jako argumenty:

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

Gdyby można było przekazać mniej argumentów, byłoby jeszcze lepiej.

Matheus Bezerra Soares
źródło
2
Czy to odpowiada na twoje pytanie? Jak stworzyć system oceniania w Pythonie?
RoadRunner

Odpowiedzi:

29

Możesz użyć modułu bisect , aby wyszukać tabelę numeryczną:

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']
św
źródło
2
Chciałbym mieć dodatkowe +1 za użycie bisect, które moim zdaniem jest używane zbyt rzadko.
norok2
4
@ norok2 Nie wydaje mi się, żeby lista 4 elementów była dobrym początkiem. W przypadku takich małych list skanowanie liniowe będzie prawdopodobnie szybsze. Plus użycie zmiennego domyślnego argumentu bez żadnych heads-up;)
schwobaseggl
1
Jasne, ale to nie boli, a biorąc pod uwagę aspekt uczenia się pytania, uważam to za całkiem odpowiednie.
norok2
2
Jest to przykład z modułu dwusiecznego
dawg
@schwobaseggl nawet dla tak małych list bisect jest szybszy. Na moim laptopie dwusieczne rozwiązanie zajmuje 1,2µs, a pętla zajmuje 1,5µs
Iftah
10

Możesz zrobić coś według następujących zasad:

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

Używa to nextz domyślnym argumentem generatora nad parami ocen utworzonymi przez zip. Jest to właściwie dokładny odpowiednik podejścia w pętli.

schwobaseggl
źródło
5

Każdej klasie można przypisać wartość progową:

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"
Nico
źródło
2
Uwaga: jeśli używasz Pythona w wersji 3.6 lub starszej, powinieneś to zrobić, sorted(grades.items())ponieważ nie gwarantuje się sortowania nagrań.
wjandrea
To nie działa niezawodnie we wszystkich wersjach Pythona. Pamiętaj, że kolejność nagrania nie jest gwarantowana. Również a dictjest niepotrzebnie ciężką strukturą danych, ponieważ liczy się kolejność, a ty i tak patrzysz na indeks (kolejność), a nie na klucz.
schwobaseggl
1
Na pewno nie jest najbardziej wydajny, ale jest prawdopodobnie najbardziej czytelny, ponieważ wszystkie znaki są pisane blisko ich progu. Raczej sugerowałbym zastąpienie dyktusa krotką par.
norok2
@schwobaseggl W przypadku tego konkretnego zadania, tak, lista krotek byłaby lepsza niż dyktowanie, ale gdyby cały ten kod działał w module, dykt pozwoliłby ci sprawdzić literę ocena -> próg.
wjandrea
1
@wjandrea Jeśli to konieczne, musisz zamienić klucze i wartości, aby zezwolić na coś takiego grades[int(score*10)/10.0], ale powinieneś użyć, Decimalponieważ zmiennoprzecinkowe są notorycznie źle działającymi klawiszami dict.
schwobaseggl
4

W tym konkretnym przypadku nie potrzebujesz zewnętrznych modułów ani generatorów. Wystarczy podstawowa matematyka (i to szybciej)!

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"
Riccardo Bucco
źródło
2

Możesz użyć np.selectz biblioteki numpy dla wielu warunków:

>> 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')
YOLO
źródło
2

Mam prosty pomysł na rozwiązanie tego:

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

Teraz,

print(convert_grade(.95))  # --> A 
print(convert_grade(.9))  # --> A
print(convert_grade(.4))  # --> F
print(convert_grade(.2))  # --> F
Taohidul Islam
źródło
1

Możesz użyć numpy.searchsorted, co dodatkowo daje tę przyjemną opcję przetwarzania wielu wyników w jednym wywołaniu:

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']
mysh
źródło
1

Dostarczyłeś prostą skrzynkę. Jeśli jednak twoja logika staje się bardziej skomplikowana, możesz potrzebować silnika reguł, aby poradzić sobie z chaosem.

Możesz wypróbować silnik Sauron Rule lub znaleźć niektóre silniki reguł Python z PYPI.

Leon
źródło
1
>>> grade = lambda score:'FFFFFFDCBAA'[int(score*100)//10]
>>> grade(0.8)
'B'
Biar Fordlander
źródło
1
Chociaż ten kod może odpowiedzieć na pytanie, lepiej byłoby dołączyć kontekst, wyjaśniając, jak to działa i kiedy go używać. Odpowiedzi zawierające tylko kod nie są przydatne na dłuższą metę.
Mustafa,
0

Możesz także zastosować podejście rekurencyjne:

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']
Młotek
źródło
0

Oto niektóre bardziej zwięzłe i mniej zrozumiałe podejścia:

Pierwsze rozwiązanie wymaga użycia funkcji podłogi z mathbiblioteki.

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

A jeśli z jakiegoś powodu importowanie mathbiblioteki Ci przeszkadza. Możesz użyć obejścia funkcji podłogi:

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

Są to nieco skomplikowane i odradzam ich używanie, chyba że rozumiesz, co się dzieje. Są to konkretne rozwiązania, które wykorzystują fakt, że przyrosty w stopniach wynoszą 0,1, co oznacza, że ​​użycie przyrostu innego niż 0,1 prawdopodobnie nie zadziałałoby przy użyciu tej techniki. Nie ma również łatwego interfejsu do mapowania ocen do ocen. Bardziej ogólne rozwiązanie, takie jak Dawg używające dwusiecznej, jest prawdopodobnie bardziej odpowiednie lub bardzo czyste rozwiązanie schwobaseggl. Nie jestem do końca pewien, dlaczego publikuję tę odpowiedź, ale jest to tylko próba rozwiązania problemu bez bibliotek (nie twierdzę, że używanie bibliotek jest złe) w jednym wierszu, co pokazuje wszechstronność Pythona.

Fizzlebert
źródło
0

Możesz użyć słownika.

Kod

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

Próbny

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

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

Jeśli wyniki faktycznie wynoszą od 0 do 1, najpierw pomnóż 100, a następnie sprawdź wynik.

pylang
źródło
0

Nadzieja może pomóc: 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”) else: print („F”)

Amanda
źródło
-3

Możesz mieć listę liczb, a następnie listę ocen, aby przejść z nią:

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

Następnie, jeśli chcesz przekonwertować określony wynik na ocenę literową, możesz to zrobić:

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

Wtedy twój końcowy wynik byłby "B".

GavinTheCrafter
źródło
3
W przypadku, gdy zastanawiasz się nad DV: to rozwiązanie nie daje możliwości uzyskania oceny jak 0.83z oceny "B". Będziesz musiał pokazać, jak przejść od wyniku do indeksu item.
schwobaseggl