j_syk
j_syk

Reputation: 6621

Python simplify checking multiple boolean conditions

I have a function that checks 3 different boolean flags and has a unique outcome for each of the 8 combinations.

"""
a | b | c | out
- + - + - + ---
0 | 0 | 0 | 0
0 | 0 | 1 | 1
0 | 1 | 0 | 2
0 | 1 | 1 | 3
1 | 0 | 0 | 4
1 | 0 | 1 | 5
1 | 1 | 0 | 6
1 | 1 | 1 | 7
"""

def do_stuff(a, b, c):
    if a:
        if b:
            if c:
                return function7()  # a, b, c
            return function6()  # a, b, !c
        if c:
            return function5()  # a, !b, c
        return function4()  # a, !b, !c
    else:
        if b:
            if c:
                return function3()  # !a, b, c
            return function2()  # !a, b, !c
        if c:
            return function1()  # !a, !b, c
        return function0()  # !a, !b, !c

I'm already short-cutting a lot of else statements since I'm returning (exiting the loop).

Is there a more DRY way to accomplish this? I could convert to binary, and do a single depth level of if/elif, but I don't want to use "magic numbers".

Also, I realize this is only 16 lines of code for 8 outcomes, but what if it were 4 variables, is there a way to increase readability/flow?

Upvotes: 0

Views: 1317

Answers (3)

mhbashari
mhbashari

Reputation: 482

First you can build a binary to decimal converter.After obtaining the new number you can call the method by function name in an string by eval function:

def function0():
    print("function0")
def function1():
    return 1;
a = eval("function1()")
print(a)

Remember that you can pass global variables to eval function unless the current global names id used.

Upvotes: -2

Mad Physicist
Mad Physicist

Reputation: 114230

You need to have some sort of mapping from outcome to function and a mapping from input to outcome. The nifty table you provide at the top, combined with the fact that bools in Python are just the integers 0 and 1 gives you the following:

outcomeToFunc {
    0: function0,
    1: function1,
    2: function2,
    3: function3,
    4: function4
    # etc...
}

def inputToOutcome(a, b, c):
    return a << 2 | b << 1 | c

def do_stuff(a, b, c):
    outcome = inputToOutcome(a, b, c)
    return outcomeToFunc[outcome]()

If you are interested in having an arbitrary number of boolean inputs, you can modify do_stuff and inputToOutcome to use varargs:

outcomeToFunc {
    0: function0,
    1: function1,
    2: function2,
    3: function3,
    4: function4
    # etc...
}

def inputToOutcome(*args):
    outcome = 0
    n = len(args) - 1
    for ind, arg in enumerate(args):
        outcome |= bool(arg) << (n - ind)
    return outcome

def do_stuff(*args):
    outcome = inputToOutcome(*args)
    try:
        return outcomeToFunc[outcome]()
    except KeyError:
        raise ValueError('Outcome {} not supported'.format(outcome))

Upvotes: 4

Robᵩ
Robᵩ

Reputation: 168616

Here are two solutions. It is up to you to decide if they are prettier than the one you presented.

def do_stuff(a, b, c):
    flags = bool(a), bool(b), bool(c)
    if flags == (True, True, True):
        return function7()
    if flags == (True, True, False):
        return function6()
    if flags == (True, False, True):
        return function5()
    if flags == (True, False, False):
        return function4()
    if flags == (False, True, True):
        return function3()
    if flags == (False, True, False):
        return function2()
    if flags == (False, False, True):
        return function1()
    if flags == (False, False, False):
        return function0()

def do_stuff(a, b, c):
    control = {
        (True, True, True): function7,
        (True, True, False): function6,
        (True, False, True): function5,
        (True, False, False): function4,
        (False, True, True): function3,
        (False, True, False): function2,
        (False, False, True): function1,
        (False, False, False): function0,
    }
    return control[bool(a), bool(b), bool(c)]()

Upvotes: 1

Related Questions