一种解决字母等级分配问题的优美,简短的方法

时间:2019-02-03 15:51:50

标签: python

我是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链接在一起。我想知道是否有更短的方法来解决这个问题。

4 个答案:

答案 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+