繁体   English   中英

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

[英]How can I simplify repetitive if-elif statements in my grading system function?

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

  • 如果score >= 0.9将打印 'A'
  • 如果score >= 0.8将打印 'B'
  • 0.7, C
  • 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')

我想知道是否有办法构建一个 function,这样复合语句就不会那么重复了。

我是一个完全的初学者,但会是这样的:

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

可能吗?

这里的目的是稍后我们可以通过只传递 scr、numbergrade 和 letter grade 作为 arguments 来调用它:

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

如果能少传arguments就更好了。

您可以使用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']

您可以按照以下方式做一些事情:

# 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创建的分数-等级对。 它实际上与您的循环方法完全相同。

您可以为每个等级分配一个阈值:

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"

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

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"

您可以使用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']

您可以将np.select库中的 np.select 用于多个条件:

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

我有一个简单的想法来解决这个问题:

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

你提供了一个简单的案例。 但是,如果您的逻辑变得越来越复杂,您可能需要一个规则引擎来处理混乱。

您可以尝试Sauron 规则引擎或从 PYPI 找到一些 Python 规则引擎。

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

除了最值得注意的解决方案的一些时间安排外,我并没有为聚会添加太多内容:

import bisect


def grade_bis(score, thresholds=(0.9, 0.8, 0.7, 0.6), grades="ABCDF"):
    i = bisect.bisect(thresholds[::-1], score)
    return grades[-i - 1]
def grade_gen(score, thresholds=(0.9, 0.8, 0.7, 0.6), grades="ABCDF"):
    return next((
        grade
        for grade, threshold in zip(grades, thresholds)
        if score >= threshold), grades[-1])
def grade_enu(score, thresholds=(0.9, 0.8, 0.7, 0.6), grades="ABCDF"):
    for i, threshold in enumerate(thresholds):
        if score >= threshold:
            return grades[i]
    return grades[-1]
def grade_alg(score, grades="ABCDF"):
    return grades[-max(int(score * 10) - 5, 0) - 1]
  • 使用if - elif - else链(本质上,OP 的方法,也没有概括):
def grade_iff(score):
    if score >= 0.9:
        return "A"
    elif score >= 0.8:
        return "B"
    elif score >= 0.7:
        return "C"
    elif score >= 0.6:
        return "D"
    else:
        return "F"

他们都给出相同的结果:

import random
random.seed(2)
scores = [round(random.random(), 2) for _ in range(10)]
print(scores)
# [0.96, 0.95, 0.06, 0.08, 0.84, 0.74, 0.67, 0.31, 0.61, 0.61]

funcs = grade_bis, grade_gen, grade_enu, grade_alg
for func in funcs:
    print(f"{func.__name__:>12}", list(map(func, scores)))
#    grade_bis ['A', 'A', 'F', 'F', 'B', 'C', 'D', 'F', 'D', 'D']
#    grade_gen ['A', 'A', 'F', 'F', 'B', 'C', 'D', 'F', 'D', 'D']
#    grade_enu ['A', 'A', 'F', 'F', 'B', 'C', 'D', 'F', 'D', 'D']
#    grade_alg ['A', 'A', 'F', 'F', 'B', 'C', 'D', 'F', 'D', 'D']
#    grade_iff ['A', 'A', 'F', 'F', 'B', 'C', 'D', 'F', 'D', 'D']

具有以下计时(在n = 100000次重复到list ):

n = 100000
scores = [random.random() for _ in range(n)]
base = list(map(funcs[0], scores))
for func in funcs:
    res = list(map(func, scores))
    is_good = base == res
    print(f"{func.__name__:>12}  {is_good}  ", end="")
    %timeit -n 4 -r 4 list(map(func, scores))
#    grade_bis  True  4 loops, best of 4: 46.1 ms per loop
#    grade_gen  True  4 loops, best of 4: 96.6 ms per loop
#    grade_enu  True  4 loops, best of 4: 54.4 ms per loop
#    grade_alg  True  4 loops, best of 4: 47.3 ms per loop
#    grade_iff  True  4 loops, best of 4: 17.1 ms per loop

表明 OP 的方法是迄今为止最快的,并且在那些可以推广到任意阈值的方法中,基于bisect的方法是当前设置中最快的。


作为function的门槛数

考虑到对于非常小的输入,线性搜索应该比二分搜索更快,查看盈亏平衡点在哪里以及确认二分搜索的这种应用呈次线性(对数)增长是很有趣的。

为此,提供了阈值数量的基准 function(不包括:

import string


n = 1000
m = len(string.ascii_uppercase)
scores = [random.random() for _ in range(n)]

timings = {}
for i in range(2, m + 1):
    breakpoints = [round(1 - x / i, 2) for x in range(1, i)]
    grades = string.ascii_uppercase[:i]
    print(grades)
    timings[i] = []
    base = [funcs[0](score, breakpoints, grades) for score in scores]
    for func in funcs[:-2]:
        res = [func(score, breakpoints, grades) for score in scores]
        is_good = base == res
        timed = %timeit -r 16 -n 16 -q -o [func(score, breakpoints, grades) for score in scores]
        timing = timed.best * 1e3
        timings[i].append(timing if is_good else None)
        print(f"{func.__name__:>24}  {is_good}  {timing:10.3f} ms")

可以用以下内容绘制:

import pandas as pd
import matplotlib.pyplot as plt


df = pd.DataFrame(data=timings, index=[func.__name__ for func in funcs[:-2]]).transpose()
df.plot(marker='o', xlabel='Input size / #', ylabel='Best timing / µs', figsize=(6, 4))
fig = plt.gcf()
fig.patch.set_facecolor('white')

生产:

bm

表明盈亏平衡点在5左右,也证实了grade_gen()grade_enu()的线性增长,以及grade_bis() () 的次线性增长。


基于 NumPy 的方法

能够处理 NumPy 的方法应该单独评估,因为它们采用不同的输入并且能够以矢量化方式处理 arrays。

您还可以使用递归方法:

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

这里有一些更简洁但更难理解的方法:

第一个解决方案需要使用math库中的地板 function。

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库困扰你。 您可以使用 function 楼层的变通方法:

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 的多功能性。

你可以使用字典。

代码

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,然后查找分数。

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

目标是构建一个程序,将分数从“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')

我想知道是否有办法构建 function 以便复合语句不会重复。

我是一个完全的初学者,但会是这样的:

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

有可能吗?

这里的意图是稍后我们可以通过只传递 scr、numbergrade 和 letter grade 为 arguments 来调用它:

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

如果可以通过更少的arguments,那就更好了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM