Как я могу упростить повторяющиеся операторы 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.