Как я могу упростить повторяющиеся операторы if-elif в своей функции системы оценок?


20

Цель состоит в том, чтобы создать программу для преобразования баллов из системы «0 в 1» в систему «F в A»:

  • Если score >= 0.9бы напечатать 'A'
  • Если score >= 0.8бы напечатать 'B'
  • 0,7, С
  • 0,6, Д
  • И любое значение ниже этой точки выведите F

Это способ построить его, и он работает в программе, но он несколько повторяется:

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

Я хотел бы знать, есть ли способ построить функцию, чтобы составные операторы не были такими же повторяющимися.

Я абсолютный новичок, но хотел бы что-то вроде:

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

возможно?

Намерение здесь состоит в том, что позже мы можем вызвать его, передав в качестве аргументов только scr, numbergrade и letter grade:

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

Если бы было возможно передать меньше аргументов, было бы еще лучше.



2
Отвечает ли это на ваш вопрос? Как создать систему оценок в Python?
RoadRunner

Ответы:


31

Вы можете использовать модуль bisect для поиска числовых таблиц:

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
Я хотел бы получить дополнительный +1 за использование bisect, которое я нахожу слишком редким.
norok2

4
@ norok2 Я не думаю, что список из 4 элементов - это то место, с которого стоит начать. Для таких небольших списков линейное сканирование, скорее всего, будет быстрее. Плюс использование изменяемого аргумента по умолчанию без каких-либо
заголовков

1
Конечно, но это не повредит, и учитывая аспект обучения в вопросе, я считаю это вполне уместным.
norok2

2
Это пример из модуля bisect
dawg

@schwobaseggl даже для таких маленьких списков делится быстрее. На моем ноутбуке решение пополам занимает 1,2 мксек, а цикл занимает 1,5 мкс
Ифта

10

Вы можете сделать что-то вроде этого:

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

Используется nextс аргументом по умолчанию для генератора над парами оценки и оценки, созданными zip. Это практически точный эквивалент вашего цикла подхода.


5

Каждому классу можно присвоить пороговое значение:

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
Обратите внимание, что если вы используете Python 3.6 или ниже, вам следует это делать, sorted(grades.items())так как не обязательно сортировать dicts.
wjandrea

Это не будет надежно работать во всех версиях Python. Обратите внимание, что порядок диктов не гарантируется. Кроме того, a dict- это излишне тяжелая структура данных, так как важен порядок, и вы в любом случае просматриваете индекс (порядок), а не ключ.
Schwobaseggl

1
Конечно, не самый эффективный, но, пожалуй, самый читаемый, так как все оценки написаны близко к их порогу. Я бы предпочел заменить диктат кортежем пар.
norok2

@schwobaseggl Для этой конкретной задачи, да, список кортежей был бы лучше, чем dict, но если бы весь этот код собирался в модуле, dict позволил бы вам искать буквенную оценку -> порог.
wjandrea

1
@wjandrea Во всяком случае, вам нужно поменять местами ключи и значения, чтобы разрешить что-то подобное grades[int(score*10)/10.0], но тогда вы должны использовать в Decimalкачестве плавающих ключей общеизвестно плохое поведение.
Schwobaseggl

5

В этом конкретном случае вам не нужны внешние модули или генераторы. Немного базовой математики достаточно (и быстрее)!

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

Вы можете использовать np.selectбиблиотеку numpy для нескольких условий:

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

У меня есть простая идея, чтобы решить это:

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

Сейчас же,

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

1

Вы могли бы использовать numpy.searchsorted, что дополнительно дает вам этот хороший вариант обработки нескольких баллов за один вызов:

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

Вы предоставили простой случай. Однако, если ваша логика усложняется, вам может понадобиться механизм правил для управления хаосом.

Вы можете попробовать движок правил Sauron или найти некоторые движки правил Python от PYPI.


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

1
Хотя этот код может ответить на вопрос, было бы лучше включить некоторый контекст, объясняющий, как он работает и когда его использовать. Ответы только для кода бесполезны в долгосрочной перспективе.
Мустафа

0

Вы также можете использовать рекурсивный подход:

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

Вот несколько более кратких и менее понятных подходов:

Первое решение требует использования функции пола из mathбиблиотеки.

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

И если по какой-то причине импорт mathбиблиотеки беспокоит вас. Вы можете использовать обходной путь для функции пола:

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

Это немного сложно, и я бы советовал не использовать их, если вы не понимаете, что происходит. Это конкретные решения, в которых используется тот факт, что приращения в оценках равны 0,1, что означает, что использование приращения, отличного от 0,1, вероятно, не будет работать с использованием этой техники. Он также не имеет простого интерфейса для сопоставления оценок с оценками. Более общее решение, такое как решение dawg с использованием bisect, вероятно, является более подходящим или очень чистым решением schwobaseggl. Я не совсем уверен, почему я публикую этот ответ, но это просто попытка решить проблему без каких-либо библиотек (я не пытаюсь сказать, что использование библиотек плохо) в одну строку, демонстрируя универсальность Python.


0

Вы можете использовать диктовку.

Код

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

демонстрация

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

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

Если оценки на самом деле находятся между 0 и 1, сначала умножьте 100, а затем посмотрите результат.


0

Надеюсь, что может помочь следующее: 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: печать ( 'D') еще: печать ( 'F')


-3

Вы могли бы иметь список чисел, а затем список оценок, чтобы пойти с ним:

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

Затем, если вы хотите преобразовать указанную оценку в буквенную оценку, вы можете сделать это:

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

Тогда ваш окончательный счет будет "B".


3
В случае, если вы задаетесь вопросом о dv's: это решение не дает никакого способа получить от оценки как 0.83до оценки "B". Вам нужно будет показать, как добраться от оценки до индекса item.
Schwobaseggl
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.