如何在评分系统功能中简化重复的if-elif语句?


19

目标是构建一个程序,将分数从“ 0到1”系统转换为“ F到A”系统:

  • 如果score >= 0.9会打印“ A”
  • 如果score >= 0.8会打印“ B”
  • 0.7,C
  • 0.6,D
  • 然后在该点以下的任何值打印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

Answers:


29

您可以使用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个元素的列表开始。对于这么小的列表,线性扫描可能会更快。加上使用没有任何提示的可变默认参数;)
schwobaseggl

1
当然可以,但这并没有影响,考虑到该问题的学习方面,我觉得它很合适。
norok2

2
这是bisect模块的示例
dawg,

@schwobaseggl甚至对于这样的小列表bisect也更快。在我的笔记本电脑上,bisect解决方案需要1.2µs,循环需要1.5µs
Iftah

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())因为不能保证对字典进行排序。
wjandrea

这并非在所有Python版本中都能可靠运行。请注意,不能保证字典的顺序。另外,a dict是不必要的繁重数据结构,因为顺序很重要,无论如何,您都是通过索引(顺序)而不是键来查找的。
schwobaseggl

1
当然不是最有效的方法,但可以说它是最易读的,因为所有标记的书写都接近其阈值。我宁愿建议用成对的元组代替字典。
norok2

@schwobaseggl对于这个特定的任务,是的,元组列表会比字典更好,但是如果所有这些代码都放在模块中,则字典将允许您查找字母等级->阈值。
wjandrea

1
@wjandrea如果有的话,您需要交换键和值以允许类似的东西grades[int(score*10)/10.0],但是您应该使用,Decimal因为浮动是众所周知的行为不佳的dict键。
schwobaseggl

4

在这种情况下,您不需要外部模块或生成器。一些基本的数学就足够了(并且更快)!

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.selectnumpy库中的多个条件:

>> 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规则引擎,也可以从PYPI找到一些Python规则引擎。


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

1
尽管此代码可以回答问题,但最好包含一些上下文,说明其工作方式和使用时间。从长远来看,纯代码答案没有用。
Mustafa

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库中的floor函数。

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以外的增量可能无法使用此技术。它还没有用于将标记映射到成绩的简便界面。更通用的解决方案(例如使用bisect的dawg解决方案)可能更合适,或者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

希望以下内容可能会有所帮助:如果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

您可以有一个数字列表,然后是一个等级列表:

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感到疑惑:此解决方案无法提供从得分0.83到成绩的方法"B"。您将必须展示如何从得分到指数item
schwobaseggl
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.