Как реализовать матрицу решений в С#

Мне нужно принять решение на основе довольно большого набора из 8 взаимозависимых условий.

           | 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

Каждое из условий от A до H может быть истинным (1), ложным (0) или нерелевантным (-) для решения.

Таким образом, при заданном входе

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

он должен оцениваться как Decision02.

Решения однозначны, поэтому из любого заданного набора входных условий ясно, какое решение должно быть принято (и в случае, который не охвачен матрицей решений, должно быть выдано исключение).

Разработчик, работавший до меня над этим проектом, пытался реализовать его в виде бегемота с вложенными if длиной в 500 строк, который, конечно, чертовски глючит и не поддается сопровождению.

Поэтому я искал лучший способ реализовать такую ​​​​логику и наткнулся на таблицы решений/таблицы поиска/управляющие таблицы.

Я нашел много генераторов таблиц решений, но ни одного фрагмента кода о том, как реализовать процесс принятия решений :(

Я могу сделать таблицу решений в базовой базе данных MSSQL, или в коде, или в xml, или в чем угодно. Мне просто нужно несколько указателей на то, как реализовать это вообще.

Как лучше всего реализовать эту логику? Словарь? Многомерный массив? Что-то совсем другое?


person Aether McLoud    schedule 03.06.2013    source источник
comment
Nullable boolean - это то, с чего я бы начал... bool? Может быть true false или null   -  person Sayse    schedule 03.06.2013
comment
@Sayse By - он имеет в виду, что это не важно, например, это может быть 1 или 0.   -  person It'sNotALie.    schedule 03.06.2013


Ответы (6)


Вы можете сделать это с помощью массивов 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; };

Теперь вы можете поместить свою матрицу в словарь следующим образом:

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});

Наконец, для каждого заданного входного массива:

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"

Это, вероятно, должно получить еще несколько проверок (например, проверка правильности размера входного массива), но должно дать вам некоторое представление. Использование Funcs также дает вам больше гибкости на случай, если вы получите четвертое состояние.

person Jan Thomä    schedule 03.06.2013
comment
Вы назвали оба результата: string и bool. - person jszigeti; 03.06.2013
comment
Спасибо, я исправил это. И это должно быть Func‹bool,bool› вместо Func‹bool›, это тоже исправлено. - person Jan Thomä; 04.06.2013

Я бы использовал двумерный массив (в нашем случае Dictionary<TKey, TValue>) из bool? — обратите внимание на ? для Nullable<bool>, который допускает 3 состояния: true, false и null. Ваш нуль может означать "отсутствие эффекта"...

Определенный массив:

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

Затем вы можете делать такие вещи, как:

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

return result;
person Haney    schedule 03.06.2013
comment
Вы правы, это был просто пример того, что можно сделать, а не ЧТО делать дословно. Вместо этого я обновлю код до OR - person Haney; 04.06.2013
comment
Хорошо, теперь вы понимаете, что при первом истинном значении остается истинным до конца цикла, и вы либо получаете то, что выиграли - по умолчанию ложно, одно истинно, все истинно - либо нет. Итак, для первого значения true просто верните значение true, а не продолжайте. - person Aristos; 04.06.2013
comment
Да, опять же это только пример того, что можно сделать в теории. Я не знаю и не хочу знать о его специфической логике. Я только показываю, как использовать вложенный словарь с помощью синтаксиса индексатора. - person Haney; 04.06.2013

Вот как бы я это сделал с моей любовью к LINQ.

Во-первых, ваши матрицы представляют собой IEnumerable<IEnumerable<bool?>>, а true означает 1, false, 0 и null неопределенные значения.

Затем вы передаете IEnumerable<bool>, который хотите проверить. Вот функция:

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();
}

(Это метод расширения, поместите его в static class :))

person It'sNotALie.    schedule 03.06.2013

Вы можете сделать это в пару строк и создать двоичный калькулятор. Итак, в приведенном ниже примере результаты = 182, чем решение D (или что угодно). Приведенное ниже соответствует вашим решениям, и все результаты будут разными.

Вот веб-сайт, который работает с бинарными файлами [http://electronicsclub.info/counting.htm] благодаря Google.

Например, 10110110 в двоичном формате равно 182 в десятичном: Числовое значение: 128 64 32 16 8 4 2 1
Двоичное число: 1 0 1 1 0 1 1 0
Десятичное значение: 128 + 0 + 32 + 16 + 0 + 4 + 2 + 0 = 182

person D..    schedule 03.06.2013

У вас может быть класс решений, представленный двумя байтовыми полями. Первый байт будет обозначать, какие условия являются истинными или ложными. Второй байт будет обозначать, какие условия являются релевантными. Кроме того, вы можете определить функцию, которая определяет, соответствует ли входной байт объекту.

Исходя из этого, вы можете создать матричный класс, который упаковывает список решений, а затем использует LINQ для поиска в списке решения, соответствующего вашему вводу.

У вас может быть такой класс решений

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
    }
}

Итак, объект для Decision01 можно определить как

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

Тогда ваш класс матрицы решений можно сделать следующим образом

class DecisionMatrix
{
    List<Decision> decisions;

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

Также может помочь создание класса Input, который упаковывает байт. Когда вы создаете объект Input с полями A-H, создается байт, соответствующий этим полям.

person Rubixus    schedule 03.06.2013

Вы можете реализовать матрицу решений в виде словаря, как показано ниже, и запросить матрицу, чтобы найти совпадение. Я использовал string.join для преобразования массива в строку. Также использовали «-» в матрице как регулярное выражение [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);
person siddharth    schedule 03.06.2013