我是Python的新手,想学习最优化的编码方法。我正在处理将字母等级分配给数字等级的经典介绍性问题。我已经知道链接一堆elif
的简单方法,但是有没有更优化的方法呢?如果说我有很多子等级,或者以后需要更改每个字母等级的条件怎么办?有没有办法为一系列值创建类似于dict的东西?
这是整个问题:
编写一个名为letter_grade的函数,该函数带有一个整数参数,表示学生的成绩。如果标记≥90,则返回'A';如果80≤标记<90,则返回'B';如果70≤标记<80,则返回'C';如果60≤标记<70,则返回'D';如果标记<60,则返回'E'。如果不是有效标记。有效标记的范围是0到100。
因此,我们学习的基本暴力方法很简单:
def letter_grade(mark):
if mark >100:
grade = None
elif mark >=90:
grade = 'A'
elif mark >= 80:
grade = 'B'
elif mark >= 70:
grade = 'C'
elif mark >= 60:
grade = 'D'
elif mark >= 0:
grade = 'E'
else:
grade = None
return grade
但是,现在让我说,直到F为止,我还有很多子等级,例如A +,A,A-等。我不想为此而将20+ elif
链接在一起。我想知道是否有更短的方法来解决这个问题。
答案 0 :(得分:1)
创建一个dict
映射字母和最低分数。然后按顺序循环浏览各种可能性,并检查分数是否匹配。
def get_grade(score):
GRADES = {None: 100, 'A': 90, 'B': 80, 'C': 50, 'D': 20, 'F': 0}
for grade, theshold in GRADES.items():
if score > threshold:
return grade
return None
请注意,这依赖于有序词典,这是3.7中的功能。在此之前,collections.OrderedDict
是可用的。另外,您可以使用两个可迭代项,一个用于等级,一个用于得分。您可以zip(('grades here', None, 'A', 'B'), ('scores here', 100, 90, 80))
来获得上述循环,也可以直接以(('grade', 'score'), (None, 100), ('A', 90), ...)
的形式创建一个循环。
答案 1 :(得分:1)
您可以在表示成绩截止分数的元组列表中使用条件next
:
grades = ((100, None), (90, 'A'), (80, 'B'), (70, 'C'), (60, 'D'), (0, 'E'))
def get_grade(mark):
return next((grade for score, grade in grades if mark >= score), None)
>>> get_grade(15)
'E'
>>> get_grade(75)
'C'
>>> get_grade(95)
'A'
(100, None)
和默认None
适用于得分大于100或小于0的得分。
不是那么短,但是恕我直言,它更好,可以进行范围检查并提出适当的例外情况:
grades = ((90, 'A'), (80, 'B'), (70, 'C'), (60, 'D'), (0, 'E'))
def get_grade(mark):
if 0 <= mark <= 100:
return next(grade for score, grade in grades if mark >= score)
raise ValueError("Mark must be between 0 and 100")
尽管这将遍历所有可能的等级,直到找到正确的等级为止,但鉴于等级的数量非常少且恒定,仍可以将其视为O(1)。如果等级/间隔的数量高得多,您可能会考虑使用bisect
对正确的间隔进行二进制搜索,正如在某些现在链接的答案中所看到的那样,但这不那么容易理解遇到令人讨厌的一次性错误。
答案 2 :(得分:1)
一个简短的解决方案是遵循以下代码
def letter_grade(mark):
if mark > 100 or mark <= 0:
return None
return chr(ord('E') - max(((mark - 50) // 10), 0))
这对每种情况都有效(!),要遵循它非常棘手。特别是,我所做的是将“标记”的截止值计算为最接近的10的倍数,然后减去50,并将该结果最小化为0(例如,max(((75 - 50) // 10), 0) = max (25 // 10, 0) = max(2, 0) = 2
)。然后,我从字母“ E”的整数表示中减去该数字,然后将结果发送回一个字符。例如,由于“ C”与“ E”相距两个,因此通过“下降得那么远”可以产生正确的字母等级。
但是。看看我解释这个解决方案花了多长时间(而且理解起来很棘手!),然后将其与理解您的解决方案有多么容易。我敢打赌,在同一行中还有一个更密集,更“简短”的解决方案,但更难理解!关键是较短的代码不一定更具可读性,并且您提出的解决方案既清晰又易于遵循,这将是我将要使用的大部分时间:)
答案 3 :(得分:0)
score / 10
的结果可以用一个整数表示从0到100的10个值,这是O(1)复杂度,因为不需要交互。
使用defaultdict:
from collections import defaultdict
lowest_grade = lambda: "E"
grades = defaultdict(lowest_grade)
grades.update({
10: "A", 9: "A", 8: "B", 7: "C", 6: "D", # Lower values get "E" by default.
})
def letter_grade(score):
# If the condition to not match function will return None.
if 0 <= score <= 100:
return grades[int(score / 10)]
if __name__ == "__main__":
print(letter_grade(100)) # A
print(letter_grade(85)) # B
print(letter_grade(65)) # D
print(letter_grade(43)) # E
...要考虑到有关性能,清晰度和可扩展性的所有评论,这是您可以最大程度地扩展可扩展性和性能(速度)来解决问题的另一种方法。
由于所有值都可以得到一个分数(0-100),因此您可以使用记忆来存储每个分数值的对应字母等级:
conf = (
(0, 59, "E"),
(60, 69, "D"),
(70, 75, "C"),
(76, 79, "C+"),
(80, 85, "B"),
(86, 89, "B+"),
(90, 95, "A"),
(96, 100, "A+"),
)
grade_memoization = [None] * 101
# This for loop is executed only once, just for setting
# "memoized" grades in grade_memoization.
for cfg in conf:
for i in range(cfg[0], cfg[1] + 1):
grade_memoization[i] = cfg[2]
# Then you can access letter-grade values on O(1), every time, simple, no
# iterators, generators or for loops for each one.
# And you can extend the this as much as you want easily.
print(grade_memoization[100]) # A
print(grade_memoization[95]) # A+
print(grade_memoization[89]) # B+
print(grade_memoization[71]) # C
print(grade_memoization[76]) # C+