Вопрос

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?

Это было полезно?

Решение

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.

Другие советы

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;

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 :) )

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

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.

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);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top