1

This question has been asked on SO and an answer was given here. However it doesn’t fully solve my problem as my problem is a little bit more complex.

I have a student’s total score which represents an integer 0 <= x <= 100. Depending on where the student’s total score lies, a given grade will be issued and some logic will run also.

Of course I could write it like so:

if 0 <= grade <= 39:
    do_f()
elif 40 <= grade <= 60:
    do_b()
elif 60 <= grade <= 100:
    do_a()

You can see the problem with this already. What if more grades are added? It becomes an if else entanglement.

How can I rewrite this using a dispatch table or any other elegant and efficient way?

Drizzle
  • 198
  • 2
  • 11
  • 4
    By the way, you can make elif statements shorter: `elif grade <= 60`, `elif grade <= 100`, which will require less maintaining. – Maria K Jul 11 '23 at 15:40

4 Answers4

1

You are right in that you do not want a clumsy-looking nested if/else construct. What you need is a control table (or map).

Something like this:

def do_a():
    return 'A'

def do_b():
    return 'B'

def do_f():
    return 'F'

# the keys must be in ascending order
FMAP = {
    39: do_f,
    60: do_b,
    100: do_a
}

def process_score(score):
    assert 0 <= score <= 100
    for k, v in FMAP.items():
        if score <= k:
            return v()

for score in 10, 39, 40, 65:
    print(score, process_score(score))

Output:

10 F
39 F
40 B
65 A

Now let's say that you want to introduce a 'C' grade for scores greater than 39 but less than 50. All you need to do is add an appropriate entry in the dictionary remembering to ensure that the keys are maintained in ascending order.

Then the dictionary would look like:

FMAP = {
        39: do_f,
        50: do_c,
        60: do_b,
        100: do_a
    }
DarkKnight
  • 19,739
  • 3
  • 6
  • 22
0

You could make a dictionary where keys are maximums for a grade-range and values are the functions. The dictionary would need to be in order of smallest-to-larger, and I think that you need python 3.7+ for dictionaries to guarantee order (early implementations may change the order of items in the dictionary).

grade_dict = {
    39: do_f,
    60: do_b,
    100: do_a
}
if score < 0 or score > 100:
    raise ValueError

for range_max, func in grade_dict.items():
    if score <= range_max:
        func()
        break

edit: add break after func(), ty to slothrop in comments.

scotscotmcc
  • 2,719
  • 1
  • 6
  • 29
0

Maybe something like this. I created mock reactions just for the sake of example

reactions = {
    39: lambda: print("1"),
    60: lambda: print("2"),
    100: lambda: print("3"),
}

def get_reaction(grade):
    for max_score, reaction in reactions.items():
        if grade <= max_score:
            reaction()
            break

get_reaction(30)
get_reaction(45)
get_reaction(61)

Output:

1
2
3
Maria K
  • 1,491
  • 1
  • 3
  • 14
0

You can use dictionary:

def do_a():
    return 'A'

def do_b():
    return 'B'

def do_f():
    return 'F'

score = int(input('Score : '))

grades = {
    0 <= score <= 39: do_f,
    40 <= score <= 60: do_b,
    61 <= score <= 100: do_a
}

result = grades[1]()
print(result)

Or something like this:

def do_a():
    return 'A'

def do_b():
    return 'B'

def do_f():
    return 'F'

scores = [-23, 56, 23, 78, 100, 105]

for x in scores:
    grades = {
        0 <= x <= 39: do_f,
        40 <= x <= 60: do_b,
        61 <= x <= 100: do_a,
    }

    result = grades.get(1, lambda :'Invalid score')
    print(result())

# Invalid score
# B
# F
# A
# A
# Invalid score
Arifa Chan
  • 947
  • 2
  • 6
  • 23