Как реализовать матрицу решений в С#
Мне нужно принять решение, основанное на довольно большом наборе из 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
Каждое из условий от А до Н может быть истинным (1), ложным (0) или нерегулярным (-) для решения.
Итак, с заданным входом
A B C D E F G H
1 0 1 0 0 1 1 1
он должен оцениваться в Decision02.
Решения недвусмысленны, поэтому из любого заданного набора условий ввода ясно, какое решение должно быть принято (и в случае, когда эта матрица не охвачена, возникает исключение).
Разработчик, который работал до меня в этом проекте, пытался реализовать это как 500-строчный длинный вложенный, если бегемот, который, конечно, глючит, и не поддерживается.
Итак, я искал лучший способ реализовать такую логику, и я столкнулся с таблицами решений/таблицами поиска/таблицами управления.
Я нашел множество генераторов таблиц решений, но не один фрагмент кода о том, как реализовать процесс принятия решений: (
Я могу составить таблицу решений в базе данных MSSQL, или в коде, или в xml, или что-то еще. Мне просто нужны некоторые указатели на то, как реализовать это вообще.
Какая наилучшая практика для реализации этой логики? Словарь? Многомерный массив? Что-то совсем другое?
Ответы
Ответ 1
Вы можете сделать это с помощью массивов 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 также дает вам дополнительную гибкость, если вы получите четвертое состояние.
Ответ 2
Я бы использовал 2D-массив (Dictionary<TKey, TValue>
в нашем случае) bool?
- обратите внимание на? для Nullable<bool>
, который допускает 3 состояния: true, false и null. Ваш null может представлять "no effect"...
Определенный массив:
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;
Ответ 3
Вот как бы я это сделал, с моей любовью к 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
:))
Ответ 4
У вас может быть класс принятия решений с двумя байтовыми полями. Первый байт будет определять, какие условия являются истинными или ложными. Второй байт будет определять, какие условия имеют значение. Кроме того, вы можете определить функцию, которая определяет, соответствует ли входной байт объекту.
Из этого вы можете создать класс матрицы, который обертывает список решений, а затем использует LINQ для поиска списка для решения, которое соответствует вашему вводу.
У вас может быть класс Decision, подобный этому
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));
}
}
Он также может помочь создать класс ввода, который обертывает байты. Когда вы создаете экземпляр объекта ввода с полями A-H, создается байт для соответствия этим полям.
Ответ 5
Вы можете реализовать матрицу решений как словарь, как показано ниже, и запросить матрицу, чтобы найти совпадение. Я использовал 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);
Ответ 6
Вы можете сделать это в нескольких строках и создать двоичный калькулятор. Итак, в примере ниже, результаты = 182, чем решение D (или то, что каждый). Ниже приведен список ваших решений и результатов, которые будут разными.
Вот веб-сайт, который проходит через Binary [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