Cómo implementar la matriz de decisión en c#
matrix logic (6)
Necesito tomar una decisión basada en un conjunto bastante grande de 8 condiciones co-dependientes.
| 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
Cada una de las condiciones de A a H puede ser verdadera (1), falsa (0) o no relevante (-) para la decisión.
Así que con una entrada de
A B C D E F G H
1 0 1 0 0 1 1 1
Debe evaluarse a Decisión02.
Las decisiones no son ambiguas, por lo que, a partir de cualquier conjunto dado de condiciones de entrada, queda claro qué decisión se debe tomar (y en un caso que no está cubierto por la matriz de decisión, se lanzará una excepción).
El desarrollador que trabajó antes que yo en este proyecto intentó implementar esto como un gigante anidado de 500 líneas, que por supuesto está lleno de errores y no se puede mantener.
Así que busqué la mejor manera de implementar tal lógica y he encontrado tablas de decisiones / tablas de búsqueda / tablas de control.
He encontrado muchos generadores de tablas de decisiones, pero no un solo código sobre cómo implementar el proceso de toma de decisiones :(
Puedo tomar la tabla de decisiones en la base de datos MSSQL subyacente, o en código, o xml, o lo que sea necesario. Sólo necesito algunos consejos sobre cómo implementar esto en absoluto.
¿Cuál es la mejor práctica para implementar esta lógica? ¿Diccionario? Matriz multidimensional? ¿Algo completamente diferente?
¿ Dictionary<TKey, TValue>
un array 2D ( Dictionary<TKey, TValue>
en nuestro caso) de bool?
- nota la ? para Nullable<bool>
que permite 3 estados: verdadero, falso y nulo. Tu nulo podría representar "sin efecto" ...
Matriz definida:
var myArray = new Dictionary<char, Dictionary<int, bool?>>();
Entonces podrías hacer cosas como:
bool result = false;
foreach (var inputPair in input)
{
// Assuming inputPair is KeyValuePair<char, int>
result |= myArray[inputPair.Key][inputPair.Value];
}
return result;
Así es como lo haría, con mi amor por LINQ.
Primero, sus matrices son IEnumerable<IEnumerable<bool?>>
, y true
significa 1, false
, 0 y null
indeterminado.
Luego pasa un IEnumerable<bool>
que desea verificar. Aquí está la función:
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();
}
(Es un método de extensión, ponlo en una static class
:))
Podrías hacerlo con matrices de 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; };
Ahora puedes poner tu matriz en un Diccionario como este:
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});
Finalmente para cada matriz de entrada dada:
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"
Probablemente debería obtener algunas verificaciones más (p. Ej., Verificar que la matriz de entrada tenga el tamaño correcto) pero debería darle una idea. El uso de Funcs también le brinda más flexibilidad en caso de que obtenga un cuarto estado.
Podrías hacerlo en un par de líneas y crear una calculadora binaria. Así que en un ejemplo a continuación, los resultados = 182 que la decisión D (o lo que cada). Lo siguiente, en línea con sus decisiones y resultados, serán todos totales diferentes.
Aquí hay un sitio web que revisa el binario [http://electronicsclub.info/counting.htm] gracias a google.
Por ejemplo, 10110110 en binario es igual a 182 en decimal: Valor de dígito: 128 64 32 16 8 4 2 1
Número binario: 1 0 1 1 0 1 1 0
Valor decimal: 128 + 0 + 32 + 16 + 0 + 4 + 2 + 0 = 182
Puede implementar la matriz de decisiones como un diccionario como se muestra a continuación y consultar en la matriz para encontrar una coincidencia. He usado string.join para convertir la matriz en una cadena. También se ha utilizado el ''-'' en la matriz como una expresión regular [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);
Puede tener una clase de decisión representada con dos campos de bytes. El primer byte designará qué condiciones son verdaderas o falsas. El segundo byte designará qué condiciones son relevantes. Además, puede definir una función que determine si un byte de entrada coincide con un objeto.
A partir de esto, puede crear una clase de matriz que contenga una lista de decisiones, luego use LINQ para buscar en la lista una decisión que coincida con su entrada.
Puedes tener tu clase de decisión como esta
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
}
}
Por lo tanto, el objeto para Decisión01 se puede definir como
Decision decision01 = new Decision()
{
Conditions = 0x55; //01010101 in binary
RelevantConditions = 0xdd; //11011101 in binary
}
Entonces tu clase de Decision Matrix se puede hacer así
class DecisionMatrix
{
List<Decision> decisions;
Decision Find(byte input)
{
return decisions.Find(d => d.IsMatch(input));
}
}
También puede ayudar a hacer una clase de entrada que envuelve un byte. Cuando crea una instancia de un objeto de entrada con los campos AH, se crea un byte para que coincida con estos campos.