I\'m basically looking for a way to access a hashtable value using a two-dimensional typed key in c#.
Eventually I would be able to do something like this
How about using a regular Dictionary with some kind of Tuple structure as a key?
public class TwoKeyDictionary<K1,K2,V>
{
private readonly Dictionary<Pair<K1,K2>, 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...
}
}
You might be able to "double-nest" your hashtables - in other words, your main Dictionary is of type Dictionary<int, Dictionary<bool, my_return_type>>
.
That accomplishes your goal of being able to use the double bracket notation in your first code snippet.
Of course, the management side is a little trickier. Every time you add an entry, you need to test if the main dictionary contains a dictionary for the primary key, and add a new dictionary if not, then add the secondary key and value to the inner Dictionary.
Look, this code works just fine:
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.Services = new Dictionary<object, Hashtable>();
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
}
Now we just need a wrapper to avoid manually doing this.Services.Add("array1", new Hashtable());
A quick and dirty way would be to create a composite key from the two pieces of information, e.g.
IDictionary<string, int> values = new Dictionary<string, int>();
int i = ...;
bool b = ...;
string key = string.Concat(i, '\0', b);
values[key] = 555;
To encapsulate this a bit better you could wrap the dictionary:
public class MyDict
{
private readonly IDictionary<string, int> values = new Dictionary<string, int>();
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);
}
}
To make this more robust, encapsulate the composite key as a type, e.g. a class that contains the two fields, ensuring you override the Equals() and GetHashCode() methods correctly.
Essentially you need to use an embedded hashtable. If you think about your
question, a hashtable with two keys is a function with two independent
variables, and f(x,y)
is 2-dimensional by definition.
But you want to use it like it were one hashtable, and not embedded hashes. So what you need to do is create an object that wraps around that embedded hashtable idea and operates like a single hash.
A couple of snags:
GetEnumerator()
method. And you need your own Iterator that will iterate correctly in 2 dimensions.I have included my code to do it:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Windows.Forms;
namespace YourProjectNameHere
{
public class Hashtable2D
{
/// <summary>
/// This is a hashtable of hashtables
/// The X dim is the root key, and the y is the internal hashes key
/// </summary>
///
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
}
}
}
This is my nested Dictionary implementation:
public class TwoKeysDictionary<K1, K2, T>:
Dictionary<K1, Dictionary<K2, T>>
{
public T this[K1 key1, K2 key2]
{
get => base.ContainsKey(key1) && base[key1].ContainsKey(key2) ? base[key1][key2] : default;
set
{
if (ContainsKey(key1) && base[key1].ContainsKey(key2))
base[key1][key2] = value;
else
Add(key1, key2, value);
}
}
public void Add(K1 key1, K2 key2, T value)
{
if (ContainsKey(key1))
{
if (base[key1].ContainsKey(key2))
throw new Exception("Couple " + key1 + "/" + key2 + " already exists!");
base[key1].Add(key2, value);
}
else
Add(key1, new Dictionary<K2, T>() { { key2, value } });
}
public bool ContainsKey(K1 key1, K2 key2) => ContainsKey(key1) && base[key1].ContainsKey(key2);
}