[英]Why does my recursive function with if-elif statements return None?
[英]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'这是构建它的方法并且它适用于程序,但它有点重复:
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'
您可以为每个等级分配一个阈值:
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'
除了最值得注意的解决方案的一些时间安排外,我并没有为聚会添加太多内容:
bisect
(改编自@dawg 的回答)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(不包括:
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')
生产:
表明盈亏平衡点在5
左右,也证实了grade_gen()
和grade_enu()
的线性增长,以及grade_bis()
() 的次线性增长。
能够处理 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'这是构建它的方法,它适用于程序,但它有些重复:
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.