Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to combine a permutation of conditional statements

So, I have a series of actions to perform, based on 4 conditional variables - lets say x,y,z & t. Each of these variables have a possible True or False value. So, that is a total of 16 possible permutations. And I need to perform a different action for each permutation.

What is the best way to do this rather than making a huge if-else construct.

Lets see a simplified example. This is how my code would look if I try to contain all the different permutations into a large if-else construct.

if (x == True):
    if (y == True):
        if (z == True):
            if (t == True):
                print ("Case 1")
            else:
                print ("Case 2")
        else:
            if (t == True):
                print ("Case 3")
            else:
                print ("Case 4")
    else:
        if (z == True):
            if (t == True):
                print ("Case 5")
            else:
                print ("Case 6")
        else:
            if (t == True):
                print ("Case 7")
            else:
                print ("Case 8")
else:
    if (y == True):
        if (z == True):
            if (t == True):
                print ("Case 9")
            else:
                print ("Case 10")
        else:
            if (t == True):
                print ("Case 11")
            else:
                print ("Case 12")
    else:
        if (z == True):
            if (t == True):
                print ("Case 13")
            else:
                print ("Case 14")
        else:
            if (t == True):
                print ("Case 15")
            else:
                print ("Case 16")

Is there any way to simplify this? Obviously, my objective for each case is more complicated than just printing "Case 1".

like image 210
joseph praful Avatar asked Mar 29 '19 17:03

joseph praful


4 Answers

You can use a map of cases to results:

cases = { (True,  True,  True,  True):  "Case 1",
      (True,  True,  True,  False): "Case 2",
      (True,  True,  False, True): "Case 3",
      (True,  True,  False, False):"Case 4",
      (True,  False, True,  True): "Case 5",
      (True,  False, True,  False):"Case 6",
      (True,  False, False, True): "Case 7",
      (True,  False, False, False):"Case 8",
      (False, True,  True,  True): "Case 9",
      (False, True,  True,  False):"Case 10",
      (False, True,  False, True): "Case 11",
      (False, True,  False, False):"Case 12",
      (False, False, True,  True): "Case 13",
      (False, False, True,  False):"Case 14",
      (False, False, False, True): "Case 15",
      (False, False, False, False):"Case 16"}

print(cases[(x,y,z,t])

If you want to do something else/different for each case, you could add a function to that map.

cases = { (True,  True,  True,  True):  foo_func,
      (True,  True,  True,  False): bar_func,
         ...}

result = cases[(x,y,x,t)](*args)

You can also use one of the masking solutions to make the code shorter, or if you have too many cases to write out, but for smaller sets of cases, this explicit representation will be clearer and easier to maintain.

like image 193
munk Avatar answered Oct 16 '22 15:10

munk


you could get your case number directly from binary manipulation of your booleans:

case = (x^1) << 3 | (y^1) << 2 | (z^1) << 1 | (t^1) + 1
print(f'Case {case}')

if you look at John Kugelman's answer you see that x, y, z, t are just the 'bits' of your case number (where True=0 and False=1)... so i construct an int setting those bits (and then add 1 because you start counting at 1).

if the numbering is arbitrary you can simplify that down to x << 3 | y << 2 | z << 1 | t and take it from there.

this is easily extensible to a larger number of boolean variables.

then to do something based on the case number i suggest you create a dictionary containing the case as key and the function or data or whatever as value. something like:

case_functions = {1: func_1, 2: func_2, ...}
res = case_functions(case)(some argument)
like image 38
hiro protagonist Avatar answered Oct 16 '22 14:10

hiro protagonist


You could shove all the values into a tuple and use 16 tuple comparisons.

if   (x, y, z, t) == (True,  True,  True,  True):  print("Case 1")
elif (x, y, z, t) == (True,  True,  True,  False): print("Case 2")
elif (x, y, z, t) == (True,  True,  False, True):  print("Case 3")
elif (x, y, z, t) == (True,  True,  False, False): print("Case 4")
elif (x, y, z, t) == (True,  False, True,  True):  print("Case 5")
elif (x, y, z, t) == (True,  False, True,  False): print("Case 6")
elif (x, y, z, t) == (True,  False, False, True):  print("Case 7")
elif (x, y, z, t) == (True,  False, False, False): print("Case 8")
elif (x, y, z, t) == (False, True,  True,  True):  print("Case 9")
elif (x, y, z, t) == (False, True,  True,  False): print("Case 10")
elif (x, y, z, t) == (False, True,  False, True):  print("Case 11")
elif (x, y, z, t) == (False, True,  False, False): print("Case 12")
elif (x, y, z, t) == (False, False, True,  True):  print("Case 13")
elif (x, y, z, t) == (False, False, True,  False): print("Case 14")
elif (x, y, z, t) == (False, False, False, True):  print("Case 15")
elif (x, y, z, t) == (False, False, False, False): print("Case 16")

This could be converted into a dict lookup or use clever binary packing tricks, but the advantages here are (a) it's straightforward and readable; (b) there's no need for lambdas or functions; and (c) you can put anything into the 16 cases.

like image 1
John Kugelman Avatar answered Oct 16 '22 16:10

John Kugelman


This is a flexible solution that offers scalability and a certain level of simplicity.

Firstly, you'll need to create the methods that will run per output. These are the "complicated" versions of your print("case X") statements

#Define your method outcomes here...
#Note that this follows a binary layout starting with 
# a + b + c + d = false
def action1():      #binary 0 (a'b'c'd')
    print("case 1")
def action2():      #binary 1 (a'b'c'd)
    print("case 2")
def action3():      #binary 2 (a'b'cd')
   print("case 3")
def action4():      #binary 3 (a'b'cd)
    print("case 4")
def action5():      #binary 4 (a'bc'd')
    print("case 5") #etc...
def action6():
    print("case 6")
def action7():
    print("case 7")
def action8():
    print("case 8")
def action9():
    print("case 9")
def action10():
    print("case 10")
def action11():
    print("case 11")
def action12():
    print("case 12")
def action13():
    print("case 13")
def action14():
    print("case 14")
def action15():
    print("case 15")
def action16():
    print("case 16")
def actionDefault():
    print("Error!")

Then, you can easily reference these specific action methods later by creating a method name list and then creating a method to reference the method list when called.

import itertools #Generates all permutations
import sys       #Allows us to get the current module

#Returns the index of the actionList we should execute
def evaluateActionIndex(varList): 
    allcombinations = itertools.product([False, True], repeat=len(varList))
    i = 0
    for subset in allcombinations: #for each of the possible combinations...
        if list(subset) == varList: #Check to see if we want to execute this index.
            return i
        i = i + 1                  #Increment the target index
    return -1                      #Execute default method (-1 index)

def performAction(index):
    actionList = [action1.__name__, action2.__name__, action3.__name__, action4.__name__, 
                  action5.__name__, action6.__name__, action7.__name__, action8.__name__,
                  action9.__name__, action10.__name__, action11.__name__, action12.__name__,
                  action13.__name__, action14.__name__, action15.__name__, action16.__name__,
                  actionDefault.__name__]
    method = getattr(sys.modules[__name__], actionList[index])  #Get method by name
    method()                                                    #Execute Method

We can perform some action by using:

#Mock up some control inputs
a = False
b = True
c = False
d = False
controlVariables = [a, b, c, d] #All Your Control Variables

#Execute control sequence
performAction(evaluateActionIndex(controlVariables))

I've tested this and it works effectively. You can add as many control variables as you need to the controlVariables list.

like image 1
armitus Avatar answered Oct 16 '22 14:10

armitus