How to implement decision matrix in c#
Asked Answered
F

6

19

I need to make a decision based on a rather large set of 8 co-dependent conditions.

           | A | B | C | D | E | F | G | H
-----------+---+---+---+---+---+---+---+---
Decision01 | 0 | 1 | - | 1 | 0 | 1 | - | 1
Decision02 | 1 | 0 | - | 0 | 0 | - | 1 | -
    ...   
Decision11 | 1 | 0 | 1 | 1 | 1 | - | 1 | 1

Each of the conditions from A to H can be true (1), false (0) or non-relevant (-) for the decision.

So with a given input of

A B C D E F G H 
1 0 1 0 0 1 1 1

it should evaluate to Decision02.

The decisions are unambiguous so from any given set of input conditions it's clear which decision has to be made (and in a case that isn't covered by the decision matrix, an exception shall be thrown).

The developer who worked before me on this project tried to implement this as a 500 line long nested-if behemoth which of course is buggy as hell and isn't maintainable.

So I searched for the best way to implement such a piece of logic and I've come upon decision tables/lookup tables/control tables.

I've found a lot of decision table generators, but not a single piece of code on how to implement the decision making process :(

I can make the decision table in the underlying MSSQL database, or in code, or xml, or whatever it takes. I just need some pointers on how to implement this at all.

What's the best practice to implement this logic? Dictionary? Multidimensional array? Something completely different?

Fixing answered 3/6, 2013 at 15:58 Comment(2)
Nullable boolean is where I would start... bool? Can be true false or nullLevanter
@Levanter By - he means its not important, eg it can be 1 or 0.Liesa
N
8

You could do it with arrays of Func.

static Func<bool,bool> isTrue = delegate(bool b) { return b; };
static Func<bool,bool> isFalse = delegate(bool b) { return !b; };
static Func<bool,bool> isIrrelevant = delegate(bool b) { return true; };

Now you could put your matrix into a Dictionary like this:

Dictionary<string,Func<bool,bool>[]> decisionMatrix = new Dictionary<string,Func<bool,bool>[]>();
// 0 | 1 | - | 1 | 0 | 1 | - | 1
matrix.Add("Decision01", new Func<bool,bool>{isFalse, isTrue, isIrrelevant, isTrue, isFalse, isTrue, isIrrelevant, isTrue});

Finally for every given input array:

bool[] input = new bool[]{ false, true, false, true, false, true, false, true}

string matchingRule = null;
foreach( var pair in matrix ) {
    bool result = true;
    for( int i = 0; i < input.Length; i++) {
       // walk over the function array and call each function with the input value
       result &= pair.Value[i](input[i]);
    }

    if (result) { // all functions returned true
        // we got a winner
        matchingRule = pair.Key;
        break;
    }
}

// matchingRule should now be "Decision01"

This should probably get some more checks (e.g. checking that the input array has the correct size) but should give you some idea. Using Funcs also gives you some more flexibility in case you get a fourth state.

Nihhi answered 3/6, 2013 at 16:41 Comment(2)
You named both your string and your bool "result"Floaty
Thank you, I fixed that. And it should be Func<bool,bool> instead of Func<bool>, fixed that as well.Taxiplane
H
2

I'd use a 2D array (Dictionary<TKey, TValue> in our case) of bool? - note the ? for Nullable<bool> which allows 3 states: true, false, and null. Your null could represent "no effect"...

Defined array:

var myArray = new Dictionary<char, Dictionary<int, bool?>>();

Then you could do things like:

bool result = false;
foreach (var inputPair in input)
{
    // Assuming inputPair is KeyValuePair<char, int>
    result |= myArray[inputPair.Key][inputPair.Value];
}

return result;
Holman answered 3/6, 2013 at 16:35 Comment(3)
You're right, it was merely an example of what could be done, not WHAT to do verbatim. I'll update the code same to an OR insteadHolman
Ok, now you understand that on the first true, is stay true for the rest of the loop, and you either is what you won -default false, one true all true- either not. So on the first true, just return true, not continue.Triciatrick
Yes, again it is only an example of what can be done in theory. I am not aware of, and don't want to be aware of, his specific logic making. I am only showing how to use the nested dictionary via indexer syntax.Holman
S
2

This is how I'd do it, with my love of LINQ.

First, your matrices are an IEnumerable<IEnumerable<bool?>>, and true means 1, false, 0 and null indeterminate.

Then you pass an IEnumerable<bool> which you want to check. Here's the function:

public IEnumerable<bool?> DecisionMatrix(this IEnumerable<bool> source, IEnumerable<IEnumerable<bool?>> options)
{
    IList<bool> sourceList = source.ToList();
    return options.Where(n => n.Count() == sourceList.Count)
        .Select(n => n.Select((x, i) => new {Value = x, Index = i}))
        .Where(x => 
            x.All(n => !(sourceList[n.Index] ^ n.Value ?? sourceList[n.Index])))
        .FirstOrDefault();
}

(It's an extension method, put it in a static class :) )

Seal answered 3/6, 2013 at 17:44 Comment(0)
H
1

You could do it in a couple of lines and create a binary calculator. So in an example below, the results = 182 than decision D (or what every). The below, lines up with your decisions and results will all be different totals.

Here is a website that goes over the Binary [http://electronicsclub.info/counting.htm] thanks google.

For example 10110110 in binary equals 182 in decimal: Digit value: 128 64 32 16 8 4 2 1
Binary number: 1 0 1 1 0 1 1 0
Decimal value: 128 + 0 + 32 + 16 + 0 + 4 + 2 + 0 = 182

Houle answered 3/6, 2013 at 17:22 Comment(0)
E
1

You can have a decision class represented with two byte fields. The first byte will designate which conditions are true or false. The second byte will designate which conditions are relevant. Additionally, you can define a function that determines if an input byte matches an object.

From this, you could create a matrix class that wraps a list of decisions, then uses LINQ to search the list for a decision that matches your input.

You can have you Decision class like this

class Decision
{
    byte Conditions;
    byte RelevantConditions;

    bool IsMatch(byte input)
    {
        byte unmatchedBits = input ^ Conditions; //matching conditions are set to 0
        unmatchedBits &= RelevantConditions; //Irrelevant conditions set to 0
        return (unmatchedBits == 0); //if any bit is 1, then the input does not match the relevant conditions
    }
}

So, the object for Decision01 can be defined as

Decision decision01 = new Decision()
{
    Conditions         = 0x55; //01010101 in binary
    RelevantConditions = 0xdd; //11011101 in binary
}

Then your Decision Matrix class can be made like this

class DecisionMatrix
{
    List<Decision> decisions;

    Decision Find(byte input)
    {
        return decisions.Find(d => d.IsMatch(input));
    }
}

It may also help to make an Input class that wraps a byte. When you instantiate an Input object with the A-H fields, a byte is created to match these fields.

Equivalence answered 3/6, 2013 at 17:26 Comment(0)
M
1

You can implement the decision matrix as a dictionary as shown below and query on the matrix to find a match. I have used string.join to convert the array into a string. Also have used the '-' in the matrix as a regex [0|1].

Dictionary<string, char[]> myMatrix = new Dictionary<string, char[]>();
myMatrix.Add("Decision01", new char[] { '0', '1', '-', '1', '0', '1', '-', '1' });
myMatrix.Add("Decision02", new char[] { '1', '0', '-', '0', '0', '-', '1', '-' });
myMatrix.Add("Decision03", new char[] { '1', '1', '1', '0', '0', '1', '1', '1' });

char[] input = new char[] { '1', '0', '1', '0', '0', '1', '1', '1' };
var decision = (from match in myMatrix
            where Regex.IsMatch(string.Join(string.Empty, input), 
                string.Join(string.Empty, match.Value).ToString().Replace("-", "[0|1]"), 
                RegexOptions.IgnoreCase)
            select match.Key).FirstOrDefault();

Console.WriteLine(decision);
Mungovan answered 3/6, 2013 at 18:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.