Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a triple equivalence dictionary?

I'm trying to make a mapping between the grade categories in the picture below. Then I would like to be able to call a function that converts a grade to the same grade in an equivalent format. Ex:

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

I thought of making three parallel lists for the equivalence and then the function would end up using a bunch of if statements. What's the best way to do this?

enter image description here

like image 205
Jonathan Avatar asked May 22 '18 15:05

Jonathan


2 Answers

I would probably do something like this, it avoids conditional branching and makes it abundantly clear what you are trying to do when you convert.

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
like image 200
MoxieBall Avatar answered Oct 09 '22 14:10

MoxieBall


You could make this an enum of results

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]

This lets you do things like:

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]

and of course:

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

You could even play with the order of the tuple ((GPA, letter grade, then range) would probably work best) and inherit enum.OrderedEnum and you could do:

a_c_minus < an_a_plus  # True
like image 29
Adam Smith Avatar answered Oct 09 '22 13:10

Adam Smith