How to convert the if else logic into dynamic selection?

Viewed 55

I am writing logic to compare a few values.

I have three lists of values and one rule list

new_values = [1,0,0,0,1,1]
old_1      = [1,1,1,0,0,1]
old_2      = [1,0,1,0,1,1]

              # when a is correct    #when b is correct        # if both are correct         # if both are wrong
rules      = ['a',                   'b',                      'combine',                          'leave']

What I am looking for is, compare new_values to old_1 and old_2 values based on that select rule from rules list.

something like this:

def logic(new_values, old_values, rules):
    
    
    rules_result = []
    
    for new_value, old_value_1, old_value_2 in zip(new_values, old_values[0], old_values[1]):
        
        if new_value == old_value_1 and new_value == old_value_2:
            
            # if both are correct
            rules_result.append(rules[2])
            
            
        elif new_value == old_value_1:
            
            # if a is correct
            rules_result.append(rules[0])
            
        
        elif new_value == old_value_2:
            
            # if b is correct
            rules_result.append(rules[1])
            
        
        elif new_value!= old_value_1 and new_value!= old_value_2:
            
            # if both are wrong
            rules_result.append(rules[3])
            
    
    return rules_result

Running this code with one rule list gives me this result :

logic(new_values, [old_1, old_2], rules)

output

['combine', 'b', 'leave', 'combine', 'b', 'combine']

I am facing issue to make this code dynamic if I have to compare more than two old values list, let say If I have three lists of old values and then my rule list will expand for each combination

new_values = [1,0,0,0,1,1]
old_1      = [1,1,1,0,0,1]
old_2      = [1,0,1,0,1,1]
old_3      = [0,0,0,1,1,1]


 
              # when a is correct   #when b is correct   # if a and b are correct        # if a  and c are correct  #if b and c are correct'  #if all three are correct  # if all three are wrong
rules      = ['a',                  'b',                 'combine a_b',                   'select c',                'combine b_c',           'select a',                'combine']

I am getting rules and values from a different function, I am looking for a rule selection function, where pass the list of old values ( example 2,3,4 list ) with new value and rule list, then dynamically compare each old list with new value list and select the rule from rule list.

How to make logic function dynamic to work on more than two old list values?

2 Answers

This problem could be solved easily if you use the concept of truth table. Your rules list defines the outcome for some boolean values. It doesn't consist of 1's and 0's so it can't be expressed by truth functions like and, or, xor but it's not a problem. You can simply rearrange your list by considering the order in the truth table:

# for 2 boolean variables, there are 2 ^ 2 = 4 possibilities
        #   ab     ab   ab     ab 
        #   00     01   10     11
rules = ["leave", "b", "a", "combine"]

You can also turn this into a dict so you don't need to comment them to remember which one is what (and as a bonus, it will look like a truth table :)):

        # ab
rules = {"00": "leave", 
         "01": "b", 
         "10": "a",
         "11": "combine"}

Now, define a function to get the related key value for your boolean variables:

def get_rule_key(reference, values):
    """ compares all the values against reference and returns a string for the result"""
    return "".join(str(int(value == reference)) for value in values)

And your logic function will be simply this:

def logic(new_values, old_values, rules):
    rules_result = []

    for new_value, *old_values in zip(new_values, *old_values):
        key = get_rule_key(new_value, old_values)
        rules_result.append(rules.get(key))

    return rules_result
print(logic(new_values, [old_1, old_2], rules))
# ['combine', 'b', 'leave', 'combine', 'b', 'combine']

For triples update your rules accordingly:

# for 3 boolean variables, there are 2 ^ 3 = 8 possibilities
         # abc
rules = { "000": "combine",
        # "001": Not defined in your rules,
          "010": "b",
          "011": "combine b_c",
          "100": "a",
          "101": "select c",
          "110": "combine a_b"}
          "111": "select a"}

print(logic(new_values, [old_1, old_2, old_3], rules))
# ['combine a_b', 'combine b_c', None, 'combine a_b', 'combine b_c', 'select a']

Notes:

  • None appears in the output because your rules doesn't define what is the output for "001" and dict.get returns None by default.
  • If you want to use a list to define the rules you have to define all the rules in order and convert the result of get_rule_key to an integer: "011" -> 3. You can manage this with int(x, base=2).

With unknown inputs it will be difficult to get this labels you specify. It would be easy to map which ouf the old values corresponds to the same new value (positionally speaking). You can use a generic test function that gets a "new" value and all "old" values on that position, map the old values to 'a'... and return which ones correlate:

new_values = [1,0,0,0,1,1]
old_1 =      [1,1,1,0,0,1]
old_2 =      [1,0,1,0,1,1]
old_3 =      [0,0,0,1,1,1]
old_4 =      [0,0,0,1,1,1]
old_5 =      [0,0,0,1,1,1] 

def test(args):
    nv, remain = args[0], list(args[1:])

    start = ord("a")
    # create dict from letter to its corresponding value
    rv = {chr(start + i):v for i,v in enumerate(remain)}

    # return tuples of the input and the matching outputs
    return ((nv,remain), [k for k,v in rv.items() if v == nv])

rv = []
for values in zip(new_values, old_1, old_2, old_3, old_4, old_5):
    rv.append(test(values))

print(*rv,sep="\n")
print([b for a,b in rv])

Output (manually spaced out):

# nv  old_1 old_2 old_3 old_4 old_5
#       a     b     c     d     e
((1, [  1,    1,    0,    0,    0]),   ['a', 'b'])
((0, [  1,    0,    0,    0,    0]),   ['b', 'c', 'd', 'e'])
((0, [  1,    1,    0,    0,    0]),   ['c', 'd', 'e'])
((0, [  0,    0,    1,    1,    1]),   ['a', 'b'])
((1, [  0,    1,    1,    1,    1]),   ['b', 'c', 'd', 'e'])
((1, [  1,    1,    1,    1,    1]),   ['a', 'b', 'c', 'd', 'e'])


[['a', 'b'], ['b', 'c', 'd', 'e'], ['c', 'd', 'e'], ['a', 'b'], 
 ['b', 'c', 'd', 'e'], ['a', 'b', 'c', 'd', 'e']]

You could then map the joined results to some output:

# mapping needs completion - you'll need to hardcode that
# but if you consider inputs up to 5 old values, you need to specify those
# combinations that can happen for 5 as 4,3,2 are automagically included
# iif you omit "all of them" as result.
mapper = {"a": "only a", "ab": "a and b", "ac":"a and c", "abcde": "all of them", 
          # complete to your statisfaction on your own} 

for inp, outp in rv:
    result = ''.join(outp)
    print(mapper.get(result , f"->'{result}' not mapped!"))

to get an output of:

a and b
->'bcde' not mapped!
->'cde' not mapped!
a and b
->'bcde' not mapped!
all of them
Related