4

How can this be converted into a dictionary?

if grade>=96.5:return 5.83
elif grade>=92.5:return 5.5
elif grade>=89.5:return 5.16
elif grade>=86.5:return 4.83
elif grade>=82.5:return 4.5
elif grade>=79.5:return 4.16
elif grade>=76.5:return 3.83
elif grade>=72.5:return 3.5
elif grade>=69.5:return 3.16
elif grade>=68.5:return 2.83
elif grade>=64.5:return 2.5
else:return 0

I know how to make basic dictionaries, however, I am not sure whether it would look something like this:

grade_checker = {
    grade>96.5:5.83
}

Thanks!

jpp
  • 159,742
  • 34
  • 281
  • 339
Daniel
  • 63
  • 5
  • 5
    Not sure why you would want a dictionary here, a function is still best suited for this. – rahlf23 Dec 13 '18 at 15:48
  • 1
    This is not valid python syntax, return should only be used inside of functions. If this is a part of a function, you should include all of your code. – d_kennetz Dec 13 '18 at 15:48
  • What version of Python are you using? – ti7 Dec 13 '18 at 16:09

10 Answers10

5

The short answer is that you should not convert this to a dictionary. This is best suited as a function, and it appears you are just missing your function definition, as I see you are using return in your code. Dictionaries are constructed from key-value pairs, and since your conditions involve >= evaluations, a dictionary is not appropriate. See the function implementation below:

def grade_checker(grade):

    if grade>=96.5: return 5.83
    elif grade>=92.5: return 5.5
    elif grade>=89.5: return 5.16
    elif grade>=86.5: return 4.83
    elif grade>=82.5: return 4.5
    elif grade>=79.5: return 4.16
    elif grade>=76.5: return 3.83
    elif grade>=72.5: return 3.5
    elif grade>=69.5: return 3.16
    elif grade>=68.5: return 2.83
    elif grade>=64.5: return 2.5
    else: return 0

grade_checker(75)
grade_checker(62)
grade_checker(94)

Returns:

3.5
0
5.5
rahlf23
  • 8,869
  • 4
  • 24
  • 54
  • I agree that dicts aren't the right way to go here, but writing a function like you did seems very verbose and repetitive. Grade-Value pairs should be stored in a sequence and the function should loop over these pairs until it finds the first match. – Felix Dec 13 '18 at 16:25
  • 1
    Completely agree with you, just wanted to provide the OP with a solution w/ minimal changes to their original code (seeing as they are likely a beginner). – rahlf23 Dec 13 '18 at 17:34
3

Without a dict, you could solve the problem like this:

import numpy as np
GRADES = np.array(
    [[96.5 , 92.5 , 89.5 , 86.5 , 82.5 , 79.5 , 76.5 , 72.5 , 69.5 , 68.5 , 64.5 ],
     [ 5.83,  5.5 ,  5.16,  4.83,  4.5 ,  4.16,  3.83,  3.5 ,  3.16, 2.83,  2.5 ]])
def get_grade(grade):
    try:
        return GRADES[1][grade > [GRADES[0]][0]
    except:
        return 0

This is preferable over a dictionary because builtin dictionaries make a guarantee of orderedness (i.e., that they will iterate in the order that keys/values were inserted) only for >= Python 3.6. Being able to run code over more Python versions is preferable to depending on a specific version detail.

Nikhil Shinday
  • 1,096
  • 1
  • 9
  • 21
3

You can use a dictionary for holding the grading information, but it does not really provide any benefit as you can not use the fast dictionary lookup with those ranges. Instead, I'd suggest using a sorted list of (points, grade) pairs and then using bisect to binary-search the matching score in O(logn).

>>> import bisect
>>> grade_ranges = [(0, 0), (64.5, 2.5), (68.5, 2.83), (69.5, 3.16), 
...                 (72.5, 3.5), (76.5, 3.83), (79.5, 4.16), (82.5, 4.5), 
...                 (86.5, 4.83), (89.5, 5.16), (92.5, 5.5), (96.5, 5.83)]
...
>>> points, grade = zip(*grade_ranges)
>>> grade[bisect.bisect(points, 96.5)-1]
5.83
>>> grade[bisect.bisect(points, 73)-1]
3.5
>>> grade[bisect.bisect(points, 30)-1]
0

Unzipping the grade_ranges to points and scores is optional here, but IMHO it's a bit cleaner that way. If you don't unzip, you will have to pass a tuple to bisect, e.g. bisect(grade_ranges, (55,))

tobias_k
  • 81,265
  • 12
  • 120
  • 179
2

This would be one way, if you really need to use a dictionary; taking dictionary keys as the condition checking values and dictionary values as the values to be returned.

grade_checker = {
    96.5: 5.83,
    92.5: 5.5,
    89.5: 5.16,
    86.5: 4.83,
    82.5: 4.5,
    79.5: 4.16,
    76.5: 3.83,
    72.5: 3.5,
    69.5: 3.16,
    68.5: 2.83,
    64.5: 2.5
}

def check_grade(grade):
    for k in grade_checker:
        if grade >= k:
            return grade_checker[k]
    return 0

Check:

>>> check_grade(45.5)
0
>>> check_grade(65.5)
2.5
>>> check_grade(95)
5.5
Austin
  • 25,759
  • 4
  • 25
  • 48
  • 2
    Only works with newer versions of Python respecting insertion-order. For other versions, you could just use a list of tuples instead of a dict, or iterate the reverse-sorted keys, or use a `collections.OrderedDict`. – tobias_k Dec 13 '18 at 16:50
2

Another option is to use range-key-dict:

from range_key_dict import RangeKeyDict

range_key_dict = RangeKeyDict({
    (96.5, 100): 5.83,
    (92.5, 96.5): 5.5,
    (89.5, 92.5): 5.16,
    (86.5, 89.5): 4.83,
    (82.5, 86.5): 4.5,
    (79.5, 82.5): 4.16,
    (76.5, 79.5): 3.83,
    (72.5, 76.5): 3.5,
    (69.5, 72.5): 3.16,
    (68.5, 69.5): 2.83,
    (64.5, 68.5): 2.5,
    (0, 64.5): 0
})

assert range_key_dict[96.5] == 5.83
assert range_key_dict[96.4] == 5.5
assert range_key_dict[96.49] == 5.5

You can install this Python package with pip install range-key-dict.

You would also need to examine the source code for the complexity, since this won't maintain O(1) hashing like regular dictionaries.

It might be easier and efficient to just use regular if statements.

RoadRunner
  • 25,803
  • 6
  • 42
  • 75
2

Dicts are especially useful if you want to store key-value pairs and want to be able to retrieve an arbitrary entry really fast. As the other answers show, you only need to go through the sequence of elements and use the value of the first one that matches. So the most straight forward (and probably most efficient) strategy is to use a sequence data type. Here's how that looks like in code:

pairs = (
    (96.5, 5.83),
    (92.5, 5.5),
    (89.5, 5.16),
    (86.5, 4.83),
    (82.5, 4.5),
    (79.5, 4.16),
    (76.5, 3.83),
    (72.5, 3.5),
    (69.5, 3.16),
    (68.5, 2.83),
    (64.5, 2.5),
)

def get_grade(g):
    for grade, value in pairs:
            if g >= grade:
                return value
    return 0

Dicts are great, but if you don't need their powers, use something simpler.

Felix
  • 6,131
  • 4
  • 24
  • 44
2

If you can use a 3rd party library, you can use Pandas via pd.cut. This will be especially efficient if you have a large number of input grades to categorise.

import pandas as pd

grade_checker = {96.5: 5.83,
                 ...,
                 64.5: 2.5}

keys, values = zip(*sorted(grade_checker.items()))
keys += (float('inf'),)  # need to add upper boundary for pd.cut input

grade = 65.5
res = pd.cut([grade], keys, labels=values).astype(float)[0]  # 2.5

See related: How to map numeric data into categories / bins in Pandas dataframe

jpp
  • 159,742
  • 34
  • 281
  • 339
1

If you are using Python before 3.6, you can use collections.OrderedDict (including Python 2.7), otherwise dict objects are natively insertion-sorted (see here for more)!

With that, you can simply iterate over your dict and return the first range match.

# python 3.6+
grade_table = {
    96.5: 5.83,
    ...
    64.5: 2.5,
}

# pre-3.6
from collections import OrderedDict
grade_table = OrderedDict((  # this is a tuple of tuples
    (96.5, 5.83),
    ...
    (64.5, 2.5),
))

def fn(student_grade):
    for grade, value in grade_table.iteritems():
        if student_grade >= grade:
            return value

    return 0  # default

Note if you expect your table to change, it probably makes sense to test if your dict is in descending order or always accept an iterable of iterables and then sort 'em (I use a tuple of tuples above, but anything of the same form should work and be easy to sort) Otherwise it will return incorrect results.

ti7
  • 16,375
  • 6
  • 40
  • 68
0

It's not possible to convert this logic into a dictionary. Dictionaries are key value pairs, so there's no way you can do the "greater than" check just by way of a lookup.

rich carter
  • 103
  • 6
  • there is a way but it will still be ugly. you could for example do `d = {(0, 10): 0}` and then do `next(v for k, v in d.items() if value in range(*k))` – Ma0 Dec 13 '18 at 15:52
  • the dictionary really just becomes a sequence of key-value pairs in that case, in which case you could store it as a sequence of objects which encapsulates the lower bound, upper bound and the mapped outcome. I actually think that's less secure because you'd need to prove that the bounds don't overlap - with the original if, then, else sequence overlapping bounds are impossible. – rich carter Dec 14 '18 at 10:47
0

I'm not a maths person but thought that interpolation might, maybe, work for this?

    from numpy import interp

    ak = [k for k in gc.keys()]
    av = [v for v in gc.values()]

    # np.interp needs values from lowest to highest
    ak.reverse()
    av.reverse()

    interp(79, ak, av)
    >>> 4.105

    interp(96, ak, av)
    >>> 5.78875

    interp(64, ak, av)
    >>> 2.5

You'd need to pad-up to 100 and pad-down because it is interpolation so your data points of interest need to be within sample'able scope.

SarahToo
  • 11
  • 1
  • 3