Hashtable con clave multidimensional en C #

Básicamente, estoy buscando una forma de acceder a un valor hashtable utilizando una clave de tipo bidimensional en c #.

Eventualmente podría hacer algo como esto

HashTable[1][false] = 5; int a = HashTable[1][false]; //a = 5 

Esto es lo que he estado intentando … no ha funcionado

 Hashtable test = new Hashtable(); test.Add(new Dictionary() { { 1, true } }, 555); Dictionary temp = new Dictionary() {{1, true}}; string testz = test[temp].ToString(); 

Creo que un mejor enfoque es encapsular los muchos campos de tu clave multidimensional en una clase / estructura. Por ejemplo

 struct Key { public readonly int Dimension1; public readonly bool Dimension2; public Key(int p1, bool p2) { Dimension1 = p1; Dimension2 = p2; } // Equals and GetHashCode ommitted } 

Ahora puede crear y usar una HashTable normal y usar esta envoltura como clave.

¿Qué hay de usar un diccionario regular con algún tipo de estructura Tuple como clave?

 public class TwoKeyDictionary { private readonly Dictionary, V> _dict; public V this[K1 k1, K2 k2] { get { return _dict[new Pair(k1,k2)]; } } private struct Pair { public K1 First; public K2 Second; public override Int32 GetHashCode() { return First.GetHashCode() ^ Second.GetHashCode(); } // ... Equals, ctor, etc... } } 

Solo en caso de que alguien esté aquí recientemente, un ejemplo de cómo hacerlo de manera rápida y sucia en .Net 4.0, tal como lo describió uno de los comentaristas.

 class Program { static void Main(string[] args) { var twoDic = new Dictionary, String>(); twoDic.Add(new Tuple(3, true), "3 and true." ); twoDic.Add(new Tuple(4, true), "4 and true." ); twoDic.Add(new Tuple(3, false), "3 and false."); // Will throw exception. Item with the same key already exists. // twoDic.Add(new Tuple(3, true), "3 and true." ); Console.WriteLine(twoDic[new Tuple(3,false)]); Console.WriteLine(twoDic[new Tuple(4,true)]); // Outputs "3 and false." and "4 and true." } } 

Creo que esto podría estar más cerca de lo que estás buscando …

 var data = new Dictionary>(); 

Puedes hacer esto en C # 7.0 ahora con las nuevas tuplas:

 // Declare var test = new Dictionary<(int, bool), int>(); // Add test.Add((1, false), 5); // Get int a = test[(1, false)]; 

Sugeriría una ligera variación en la solución de jachymko que te permitirá evitar crear una clase para pares de claves. En su lugar, envuelva un diccionario privado de diccionarios, así:

 public class MultiDictionary { private Dictionary> dict = new Dictionary>(); public V this[K1 key1, K2 key2] { get { return dict[key1][key2]; } set { if (!dict.ContainsKey(key1)) { dict[key1] = new Dictionary(); } dict[key1][key2] = value; } } } 

necesita una clase de clave para el dictonario que implemente gethashcode correctamente. Y puede ampliar dictonary para permitirle acceder de una manera amigable.

la clase keypair

 public class KeyPair { public KeyPair(Tkey1 key1, Tkey2 key2) { Key1 = key1; Key2 = key2; } public Tkey1 Key1 { get; set; } public Tkey2 Key2 { get; set; } public override int GetHashCode() { return Key1.GetHashCode() ^ Key2.GetHashCode(); } public override bool Equals(object obj) { KeyPair o = obj as KeyPair; if (o == null) return false; else return Key1.Equals(o.Key1) && Key2.Equals(o.Key2); } } 

extender Dictonary <>

 public class KeyPairDictonary : Dictionary, Tvalue> { public Tvalue this[Tkey1 key1, Tkey2 key2] { get { return this[new KeyPair(key1, key2)]; } set { this[new KeyPair(key1, key2)] = value; } } } 

y lo usas así

  KeyPairDictonary dict = new KeyPairDictonary(); dict[1, false] = "test"; string test = dict[1, false]; 

Sugeriría que crease una pequeña clase personalizada que exponga las propiedades bool e int, y anule sus métodos GetHashCode e Igual, y luego use esto como la clave.

Esencialmente necesitas usar una tabla hash incrustada. Si piensa en su pregunta, una tabla hash con dos claves es una función con dos variables independientes, y f(x,y) es bidimensional por definición.

Pero desea usarlo como si fuera una tabla hash, y no hashes incrustados. Entonces, lo que tienes que hacer es crear un objeto que se ajuste a la idea de hash embedded y funcione como un solo hash.

Un par de inconvenientes:

  • Desea iterar sobre él, por lo que debe sobrescribir el método GetEnumerator() . Y necesita su propio iterador que iterará correctamente en 2 dimensiones.
  • Debe hacer más comprobaciones para asegurarse de que no haya duplicados.

He incluido mi código para hacerlo:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections; using System.Windows.Forms; namespace YourProjectNameHere { public class Hashtable2D { ///  /// This is a hashtable of hashtables /// The X dim is the root key, and the y is the internal hashes key ///  /// private Hashtable root = new Hashtable(); public bool overwriteDuplicates = false; public bool alertOnDuplicates = true; public void Add(object key_x, object key_y, object toStore) { if(root[key_x]!=null)//If key_x has already been entered { Hashtable tempHT = (Hashtable)root[key_x];//IF the hash table does not exist then focus will skip to the catch statement if (tempHT[key_y] == null) tempHT.Add(key_y, toStore); else handleDuplicate(tempHT, key_y, toStore); }else{//Making a new hashtable Hashtable tempHT = new Hashtable(); tempHT.Add(key_y, toStore); root.Add(key_x, tempHT); } } public void Remove(object key_x, object key_y) { try{ ((Hashtable)root[key_x]).Remove(key_y); }catch(Exception e){ MessageBox.Show("That item does not exist"); } } public void handleDuplicate (Hashtable tempHT, object key_y, object toStore) { if (alertOnDuplicates) MessageBox.Show("This Item already Exists in the collection"); if (overwriteDuplicates) { tempHT.Remove(key_y); tempHT.Add(key_y,toStore); } } public object getItem(object key_x, object key_y) { Hashtable tempHT = (Hashtable)root[key_x]; return tempHT[key_y]; } public ClassEnumerator GetEnumerator() { return new ClassEnumerator(root); } public class ClassEnumerator : IEnumerator { private Hashtable ht; private IEnumerator iEnumRoot; private Hashtable innerHt; private IEnumerator iEnumInner; public ClassEnumerator(Hashtable _ht) { ht = _ht; iEnumRoot = ht.GetEnumerator(); iEnumRoot.MoveNext();//THIS ASSUMES THAT THERE IS AT LEAST ONE ITEM innerHt = (Hashtable)((DictionaryEntry)iEnumRoot.Current).Value; iEnumInner = innerHt.GetEnumerator(); } #region IEnumerator Members public void Reset() { iEnumRoot = ht.GetEnumerator(); } public object Current { get { return iEnumInner.Current; } } public bool MoveNext() { if(!iEnumInner.MoveNext()) { if (!iEnumRoot.MoveNext()) return false; innerHt = (Hashtable)((DictionaryEntry)iEnumRoot.Current).Value; iEnumInner = innerHt.GetEnumerator(); iEnumInner.MoveNext(); } return true; } #endregion } } } 

Es posible que pueda “anidar doblemente” sus hashtables; en otras palabras, su diccionario principal es de tipo Dictionary> .

Esto logra su objective de poder usar la notación de doble corchete en su primer fragmento de código.

Por supuesto, el lado de la administración es un poco más complicado. Cada vez que agrega una entrada, debe probar si el diccionario principal contiene un diccionario para la clave principal y agregar un nuevo diccionario, de lo contrario, agregue la clave secundaria y el valor al diccionario interno.

¿Podría usar un Dictionary,int> ?

Envuelva su clave bidimensional en un type separado y use ese tipo como clave. También considere anular los métodos GetHashCode() y Equals() . Preferiblemente use Dictionary<> lugar de HashTable ya que aparentemente puede usar eso.

Aquí hay un ejemplo , puedes usar una Hashtable normal en lugar de la que yo usé.

Una manera rápida y sucia sería crear una clave compuesta a partir de las dos piezas de información, por ejemplo

 IDictionary values = new Dictionary(); int i = ...; bool b = ...; string key = string.Concat(i, '\0', b); values[key] = 555; 

Para encapsular esto un poco mejor podrías envolver el diccionario:

 public class MyDict { private readonly IDictionary values = new Dictionary(); public int this[int i, bool b] { get { string key = BuildKey(i, b); return values[key]; } set { string key = BuildKey(i, b); values[key] = value; } } private static string BuildKey(int i, bool b) { return string.Concat(i, '\0', b); } } 

Para hacer esto más robusto, encapsula la clave compuesta como un tipo, por ejemplo, una clase que contiene los dos campos, asegurándote de anular correctamente los métodos Equals () y GetHashCode ().

Mira, este código funciona bien:

  public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { this.Services = new Dictionary(); this.Services.Add("array1", new Hashtable()); this.Services["array1"]["qwe"] = "123"; this.Services["array1"][22] = 223; object zz = null; zz = this.Services["array1"]["qwe"]; MessageBox.Show(zz.ToString()); // shows qwe zz = this.Services["array1"][22]; MessageBox.Show(zz.ToString()); // shows 22 } 

Ahora solo necesitamos un contenedor para evitar hacer esto manualmente. Services.Add (“array1”, new Hashtable ());

Creo que la forma más fácil de hacerlo ahora es usar Tupple.Create y ValueTuple.Create:

 > var k1 = Tuple.Create("test", int.MinValue, DateTime.MinValue, double.MinValue); > var k2 = Tuple.Create("test", int.MinValue, DateTime.MinValue, double.MinValue); > var dict = new Dictionary(); > dict.Add(k1, "item"); > dict.Add(k2, "item"); An item with the same key has already been added.... > dict[k1] == dict[k2] true 

o use la nueva syntax de tupla c # 7 para crear tuplas:

 var k = (item1: "value1", item2: 123);