如何制作一个三元等价词典?

6

我正在尝试将下面图片中的等级类别进行映射。然后,我希望能够调用一个函数,将一个等级转换为相同格式的等效等级。例如:

def convert(num, letter, gpa):
    """Converts a grade into an equivalent grade. The desired output will be 
    specified by -1 and the grade format not to be involved in the conversion 
    will be specified by None. When converting to GPA, the minimum of the gpa 
    range will be returned."""
    >>> convert(83, None, -1)
    >>> 'A-'
    >>>convert(-1, 'B+', None) 
    >>>77

我考虑为等价性创建三个并列列表,然后函数最终会使用一堆if语句。最佳方法是什么?

enter image description here


2
数据库中加入一张表怎么样? - Sandesh Gupta
由于您无法从第3列转到第1列,因此对我来说最合理的做法是拥有2个字典,两者的键都是字母等级。 {'A +':(90,100),'A':(85,89)},您需要考虑如何处理89.5等分数。 - mauve
2
@Jonathan 为什么不是85? - Jacob G.
@Jonathan,你是否一直使用None -1约定进行转换,或者具有不同签名的转换函数是否可以? - MoxieBall
@MoxieBall,使用不同的约定也可以。我只是认为那个约定比较简单。 - Jonathan
显示剩余8条评论
3个回答

4
我可能会这样做,它避免了条件分支,并且在转换时清楚地表明了你要做什么。
class GradeRange:
    def __init__(self, pct, ltr, gpa):
        self.pct = pct
        self.ltr = ltr
        self.gpa = gpa

class GradeTable:
    def __init__(self):
        self.ranges = [
            GradeRange(range(0,  50), 'F',  0.0),
            GradeRange(range(50, 53), 'D-', 0.7),
            GradeRange(range(53, 57), 'D',  1.0),
            GradeRange(range(57, 60),  'D+', 1.3),
            GradeRange(range(60, 63), 'C-', 1.7),
            GradeRange(range(63, 67), 'C', 2.0),
            GradeRange(range(67, 70), 'C+', 2.3),
            GradeRange(range(70, 73), 'B-', 2.7),
            GradeRange(range(73, 77), 'B', 3.0),
            GradeRange(range(77, 80), 'B+', 3.3),
            GradeRange(range(80, 85), 'A-', 3.7),
            GradeRange(range(85, 90), 'A', 4.0),
            GradeRange(range(90, 101), 'A+', 4.0),
        ]

    def convert_pct(self, pct):
        for r in self.ranges:
            if pct in r.pct:
                return r.ltr, r.gpa

    def convert_ltr(self, ltr):
        for r in self.ranges:
            if r.ltr == ltr:
                return r.pct[0], r.gpa

    def convert_gpa(self, gpa):
        for r in self.ranges:
            if r.gpa == gpa:
                return r.pct[0], r.ltr

谢谢,这看起来非常不错! - Jonathan
由于GradeRange仅在GradeTable内部使用(且其输出始终为元组),因此将其定义在GradeTable内部似乎是合理的。另外:为什么您需要多个GradeTables?也许可以调整它,以便__init__接受包含元组列表来覆盖self.ranges的参数? - Adam Smith
我做了这个,但没有使用类。看起来并不是必要的。 - Jonathan
1
@Adam Smith只是不想将它们标识为'GradeTable.GradeRange'。你是对的,可能不需要多个,并且接受元组列表肯定会很好,只是似乎没有必要回答有关一般组织的问题。 - MoxieBall

2
您可以将此转换为结果的枚举。
from enum import Enum

class GradeResults(Enum):
    A_PLUS = ("A+", 4.3, range(90, 101))
    A = ("A", 4.0, range(85, 90))
    A_MINUS = ("A-", 3.7, range(80, 85))
    # etc

    @classmethod
    def from_lettergrade(cls, lett):
        for gr in cls:
            if lett == gr.lettergrade:
                return gr
        raise ValueError("Invalid letter grade.")

    @classmethod
    def from_gpa(cls, gpa):
        for gr in cls:
            if gpa == gr.gpa:
                return gr
        raise ValueError("Invalid GPA.")

    @classmethod
    def from_percentage(cls, pct):
        for gr in cls:
            if pct in gr.percentage:
                return gr
        raise ValueError("Percentage out of range.")

    @property
    def lettergrade(self):
        return self.value[0]

    @property
    def gpa(self):
        return self.value[1]

    @property
    def percentage(self):
        return self.value[2]

这使您可以执行以下操作:
result = GradeResults.from_gpa(4.0)
# result is now GradeResults.A

result.percentage
# range(85, 90)

class_grades = [GradeResults.from_percentage(pct).lettergrade
                for pct in some_existing_list_of_class_percentages]

当然,还有:

an_a_plus = GradeResults["A_PLUS"]  # or GradeResults.A_PLUS
a_c_minus = GradeResults.C_MINUS
a_c_minus == an_a_plus  # False

您甚至可以玩弄元组的顺序((GPA, letter grade, then range)可能是最好的),并继承enum.OrderedEnum,然后您可以执行:

a_c_minus < an_a_plus  # True

1
只是一些建议:
grades = (
 ((90, 100), 'A+', 4.0),
 ((85, 89), 'A', 4.0),
 ((80, 84), 'A-', 3.7),
 ((77, 79), 'B+', 3.3),
 ((73, 76), 'B', 3.0),
 ((70, 72), 'B-', 2.7),
 ((67, 69), 'C+', 2.3),
 ((63, 66), 'C', 2.0),
 ((60, 62), 'C-', 1.7),
 ((57, 59), 'D+', 1.3),
 ((53, 56), 'D', 1.0),
 ((50, 52), 'D-', 0.7),
 ((0, 49), 'F', 0.0))

def convert(num=None, letter=None, gpa=None):
    for scores, alpha, number in grades:
        low, high = scores
        if (
            (num is None or low <= num <= high) and
            (letter is None or letter == alpha) and
            (gpa is None or gpa >= number)): break
    else:
        return 'No match'
    return scores, alpha, number

convert(num = 83)

>>   ((80, 84), 'A-', 3.7)

convert(letter = 'B+')

>>   ((77, 79), 'B+', 3.3)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接