c# dictionary switch-statement nested control-structure

c#Anidado diccionario de diferentes profundidades



dictionary switch-statement (5)

Dado que está buscando una coincidencia parcial en la clave, no podrá lograrlo con un solo diccionario. Este es el por qué:

Supongamos que tienes algún tipo de clase de "regla". Lo llamaremos "Clave". Puede crear una instancia de esta manera:

Key.Create(0) // this "rule" would match any query key starting with 0 (e.g., {0}, {0, 1}, or {0, 1, 9, 2, 23, 243})

Ahora suponga que desea consultarlo utilizando algún tipo de clase de "hecho" o "clave de consulta". Como consulta un diccionario utilizando el tipo de valor que se usó como clave durante la operación Agregar, debería reutilizar el mismo tipo:

Key.Create(0, 2, 13) // this fact should be matched by rules {0}, {0,2} or {0, 2, 13}

Ahora vas a intentar obtener el valor:

var value = map[Key.Create(0, 2, 13)]

La clase Key podría anular Equals para permitir la coincidencia parcial de clave. Sin embargo, el diccionario utilizará primero el código hash y el código hash para Key.Create (0, 2, 13) nunca coincidirá con el hascode de Key.Create (0). No podrá evitar esto utilizando ningún tipo de base / derivado tampoco.

La mejor opción sería rodar tu propia clase. Algo como esto debería hacer:

class ResultMap { public void Add(int[] key, string value) { Debug.Assert(key != null); Debug.Assert(key.Length > 0); var currentMap = _root; foreach (var i in key.Take(key.Length - 1)) { object levelValue; if (currentMap.TryGetValue(i, out levelValue)) { currentMap = levelValue as Dictionary<int, object>; if (currentMap == null) throw new Exception("A rule is already defined for this key."); } else { var newMap = new Dictionary<int, object>(); currentMap.Add(i, newMap); currentMap = newMap; } } var leaf = key[key.Length - 1]; if (currentMap.ContainsKey(leaf)) throw new Exception("A rule is already defined for this key."); currentMap.Add(leaf, value); } public string TryGetValue(params int[] key) { Debug.Assert(key != null); Debug.Assert(key.Length > 0); var currentMap = _root; foreach (var i in key) { object levelValue; if (!currentMap.TryGetValue(i, out levelValue)) return null; currentMap = levelValue as Dictionary<int, object>; if (currentMap == null) return (string) levelValue; } return null; } private readonly Dictionary<int, object> _root = new Dictionary<int, object>(); }

Aquí hay una prueba unitaria:

public void Test() { var resultMap = new ResultMap(); resultMap.Add(new[] {0}, "a0"); resultMap.Add(new[] {1, 0}, "b0"); resultMap.Add(new[] {1, 1, 0}, "c0"); resultMap.Add(new[] {1, 1, 1}, "c1"); resultMap.Add(new[] {1, 2}, "b2"); resultMap.Add(new[] {2}, "a2"); Debug.Assert("a0" == resultMap.TryGetValue(0)); Debug.Assert("a0" == resultMap.TryGetValue(0, 0)); Debug.Assert("a0" == resultMap.TryGetValue(0, 1)); Debug.Assert("a0" == resultMap.TryGetValue(0, 2)); Debug.Assert("a0" == resultMap.TryGetValue(0, 0, 0)); Debug.Assert("a0" == resultMap.TryGetValue(0, 0, 1)); Debug.Assert("a0" == resultMap.TryGetValue(0, 0, 2)); Debug.Assert("a0" == resultMap.TryGetValue(0, 1, 0)); Debug.Assert("a0" == resultMap.TryGetValue(0, 1, 1)); Debug.Assert("a0" == resultMap.TryGetValue(0, 1, 2)); Debug.Assert("a0" == resultMap.TryGetValue(0, 2, 0)); Debug.Assert("a0" == resultMap.TryGetValue(0, 2, 1)); Debug.Assert("a0" == resultMap.TryGetValue(0, 2, 2)); Debug.Assert(null == resultMap.TryGetValue(1)); Debug.Assert("b0" == resultMap.TryGetValue(1, 0)); Debug.Assert(null == resultMap.TryGetValue(1, 1)); Debug.Assert("b2" == resultMap.TryGetValue(1, 2)); Debug.Assert("b0" == resultMap.TryGetValue(1, 0, 0)); Debug.Assert("b0" == resultMap.TryGetValue(1, 0, 1)); Debug.Assert("b0" == resultMap.TryGetValue(1, 0, 2)); Debug.Assert("c0" == resultMap.TryGetValue(1, 1, 0)); Debug.Assert("c1" == resultMap.TryGetValue(1, 1, 1)); Debug.Assert(null == resultMap.TryGetValue(1, 1, 2)); Debug.Assert("b2" == resultMap.TryGetValue(1, 2, 0)); Debug.Assert("b2" == resultMap.TryGetValue(1, 2, 1)); Debug.Assert("b2" == resultMap.TryGetValue(1, 2, 2)); Debug.Assert("a2" == resultMap.TryGetValue(2)); Debug.Assert("a2" == resultMap.TryGetValue(2, 0)); Debug.Assert("a2" == resultMap.TryGetValue(2, 1)); Debug.Assert("a2" == resultMap.TryGetValue(2, 2)); Debug.Assert("a2" == resultMap.TryGetValue(2, 0, 0)); Debug.Assert("a2" == resultMap.TryGetValue(2, 0, 1)); Debug.Assert("a2" == resultMap.TryGetValue(2, 0, 2)); Debug.Assert("a2" == resultMap.TryGetValue(2, 1, 0)); Debug.Assert("a2" == resultMap.TryGetValue(2, 1, 1)); Debug.Assert("a2" == resultMap.TryGetValue(2, 1, 2)); Debug.Assert("a2" == resultMap.TryGetValue(2, 2, 0)); Debug.Assert("a2" == resultMap.TryGetValue(2, 2, 1)); Debug.Assert("a2" == resultMap.TryGetValue(2, 2, 2)); }

Esencialmente, lo que necesito es la asignación de diferentes variables int a un diccionario. O al menos esa es la forma en que estaba pensando hacerlo. La manera más fácil en que puedo pensar para hacerlo es explicar qué quiero usar las declaraciones de cambio.

string s = ""; int a = 1; int b = 2; int c = 0; switch (a){ case 0: s = "a0"; break; case 1: switch (b){ case 0: s = "b0"; break case 1: switch (c){ case 0: s = "c0"; break; case 1: s = "c1"; break; } break case 2: s = "b2"; break; } break; case 2: s = "a2"; break; }

Esta es una versión simplificada en aras de la brevedad, en la que de otra manera podrías tener muchos nidos y en más de uno de los casos y otras cosas. Estaba pensando que una buena solución para esto sería un diccionario para seleccionar rápidamente el valor correcto, pero eso realmente no anidaría bien porque la mayoría de los nidos internos de un diccionario anidado no necesitarían tener valores.

La razón por la que pensé primero en los diccionarios es porque la sintaxis de declaración similar a la siguiente sería agradable (que es similar a lo que sería para un diccionario de diccionarios).

thing = { {0, "a0"}, {1, { {0, "b0"}, {1, { {0, "c0"}, {1, "c1"} } }, {2, "b2"} } }, {2, "a2"} } // Next line is sort of hopeful but potentially unrealistic syntax s = thing[a][b][c]; // or = thing(a,b,c);

EDITAR: Esa no es una sintaxis de declaración requerida, pero es corta y fácil de entender, que es lo que estoy buscando.

EDITAR: o LINQ, he visto muchas sugerencias de LINQ para preguntas similares, pero no estoy particularmente familiarizado con esto.


Entonces, el problema no es tan fácil como podría parecer a primera vista. Lo más importante que veo cuando lo veo es el patrón compuesto, así que comenzaremos con una interfaz que puede exponer la funcionalidad que necesitamos:

public interface INode<TParam, TResult> { TResult GetValue(TParam[] parameters, int depth); }

Lo hice genérico en lugar de codificación dura en los parámetros int y el valor de retorno de string , para hacer esto más reutilizable desde el punto de vista de MultiKeyLookup propósito general.

Luego tenemos el caso simple, los nodos Leaf que simplemente devuelven un valor específico independientemente de cuáles sean los parámetros:

class Leaf<TParam, TResult> : INode<TParam, TResult> { private TResult value; public Leaf(TResult value) { this.value = value; } public TResult GetValue(TParam[] parameters, int depth) { return value; } }

Entonces tenemos el caso menos trivial. La clase de Node adecuada. Toma varios valores y luego asigna cada uno de esos valores a un objeto INode . Ahí es donde sucede la magia. El INode que se asigna puede ser un nodo hoja con solo un valor específico, o podría ser otro nodo. Luego, cuando se le pide que obtenga un valor, simplemente asigna el parámetro de entrada a la profundidad adecuada y en un feudo recursivo obtiene el valor del INode para ese valor:

class Node<TParam, TResult> : INode<TParam, TResult> { //private Tuple<TParam, INode<TParam, TResult>>[] values; private Dictionary<TParam, INode<TParam, TResult>> lookup; public Node(params Tuple<TParam, INode<TParam, TResult>>[] values) { lookup = values.ToDictionary(pair => pair.Item1, pair => pair.Item2); } public TResult GetValue(TParam[] parameters, int depth) { return lookup[parameters[depth]].GetValue(parameters, depth + 1); } }

Entonces, en este punto podríamos haber terminado. Aquí hay un mapeo de ejemplo (ligeramente simplificado):

var node = new Node<int, string>( Tuple.Create(0, (INode<int, string>)new Leaf<int, string>("a0")), Tuple.Create(1, (INode<int, string>)new Node<int, string>( Tuple.Create(0, (INode<int, string>)new Leaf<int, string>("b0"))))); Console.WriteLine(node.GetValue(new int[] { 0 }, 0)); //prints a0

Ahora que es un desastre. En particular, tiene un montón de especificaciones genéricas de argumentos que sabemos que siempre serán las mismas, así como la necesidad de convertir cada tipo de INode en el tipo de interfaz para que la Tuple esté correctamente tipada.

Para hacerlo más fácil, creé una clase de "generador", MultiKeyLookup . Tendrá algunos métodos auxiliares para crear una hoja y un nodo, de modo que los argumentos genéricos se puedan especificar una vez para esta clase. Además, dado que tanto Leaf como Node no serán necesarios gracias a estos constructores, he hecho que ambas clases sean clases internas privadas de MultiKeyLookup , además de contener esas dos clases también tiene:

public class MultiKeyLookup<TParam, TResult> { public INode<TParam, TResult> CreateLeaf(TResult result) { return new Leaf<TParam, TResult>(result); } public INode<TParam, TResult> CreateNode( params Tuple<TParam, INode<TParam, TResult>>[] values) { return new Node<TParam, TResult>(values); } public INode<TParam, TResult> Root { get; set; } public TResult GetValue(TParam[] parameters) { return Root.GetValue(parameters, 0); } //definition of Leaf goes here //definition of Node goes here }

Usando esta clase ahora podemos escribir:

var map = new MultiKeyLookup<int, string>(); map.Root = map.CreateNode( Tuple.Create(0, map.CreateLeaf("a0")), Tuple.Create(1, map.CreateNode( Tuple.Create(0, map.CreateLeaf("b0")), Tuple.Create(1, map.CreateNode( Tuple.Create(0, map.CreateLeaf("c0")), Tuple.Create(1, map.CreateLeaf("c1")))), Tuple.Create(2, map.CreateLeaf("b2")))), Tuple.Create(2, map.CreateLeaf("a2"))); Console.WriteLine(map.GetValue(new int[] { 0 })); //prints a0 Console.WriteLine(map.GetValue(new int[] { 0, 0, 4 })); //prints a0 Console.WriteLine(map.GetValue(new int[] { 1, 0 })); // prints b0 Console.WriteLine(map.GetValue(new int[] { 1, 1, 0 })); //prints c0

Tenga en cuenta que esta es la creación completa de lo que definió en el OP, no es un ejemplo simplificado.


Si puede conocer su "estructura clave" con anticipación, podría ser más barato usar un Dictionary<string, string> y generar una clave que es una concatenación de las 3 partes: "ABC" ... Evita el nido y ofrece búsquedas directas.

Si sabe que a = 1, b = 2 yc = 3, por ejemplo, puede concatenarlos a una cadena de "123" y esa es su clave para la búsqueda del diccionario. Esto es esencialmente cómo HttpContext Caching y .NET 4.0 MemoryCache también funcionan.

EDITAR:

Si no siempre tiene los 3 valores, use una cadena. Formatee con una estructura clave que proporcione un divisor / separador entre los valores. En general, esto es una mejor práctica, de lo contrario, puede tener colisiones de teclas fácilmente:

private const string _keyFormat = "{0}_{1}_{2}"; private string GenerateKey(object a, object b, object c) { return string.Format(_keyFormat, a, b, c); }


Tal vez use clases como esta:

public class A { public string result; public A(int case) { if(case == 0) { this.result = "a0"; } else if(case == 2) { this.result = "a2"; } else { return new B(case).result; } } } public class B { public string result; public B(int case) { if(case == 0) { this.result = "b0"; } else if(case == 2) { this.result = "b2" } else { return new C(case).result; } } } public class C { public string result; public C(int case) { if(case == 0) { this.result = "C0"; } else { this.result = "c1"; } } }


Sé que ya elegiste una respuesta, pero se me ocurrió una idea nueva y creo que es genial. Usar diccionarios anidados de claves int y valores de objeto de la siguiente manera:

Dictionary<int, object> baseDictionary = new Dictionary<int, object>(); baseDictionary.Add(0, new object[] { "a1" }); baseDictionary.Add(1, new Dictionary<int, object>()); baseDictionary.Add(2, new object[] { "a2" }); Dictionary<int, object> childDictionary = baseDictionary[1] as Dictionary<int, object>; childDictionary.Add(0, new object[] { "b1" }); childDictionary.Add(1, new Dictionary<int, object>()); childDictionary.Add(2, new object[] { "b2" }); Dictionary<int, object> childTwoDictionary = childDictionary[1] as Dictionary<int, object>; childTwoDictionary.Add(0, new object[] { "c1" }); childTwoDictionary.Add(1, new object[] { "c2" });

A continuación, acceda al registro que desee utilizando un método de recursión con una matriz de teclas como esta:

private object GetResult(int keyIndex, int[] keys, Dictionary<int, object> inputDictionary) { Dictionary<int, object> nextDictionary = inputDictionary[keys[keyIndex]] as Dictionary<int, object>; object result; if (nextDictionary != null && keyIndex < keys.Length) { keyIndex++; return GetResult(keyIndex, keys, nextDictionary); } else if(!string.IsNullOrEmpty(inputDictionary[keys[keyIndex]].ToString())) { result = inputDictionary[keys[keyIndex]] as object; keyIndex++; return result; } return new object[] { "Failed" }; }

y llámalo de la siguiente manera:

private void simpleButton1_Click(object sender, EventArgs e) { int keyIndex = 0; int[] keys = { 1, 1, 1 }; object result = this.GetResult(keyIndex, keys, this.baseDictionary); labelControl1.Text = (((object[])(result))[0]).ToString(); }