Hashtable with MultiDimensional Key in C#

87,200

Solution 1

I think a better approach is to encapsulate the many fields of your multi-dimensional key into a class / struct. For example

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

Now you can create and use a normal HashTable and use this wrapper as a Key.

Solution 2

You can do this in C# 7.0 now with the new tuples:

// Declare
var test = new Dictionary<(int, bool), int>();

// Add
test.Add((1, false), 5);

// Get
int a = test[(1, false)];

Solution 3

I think this might be closer to what you're looking for...

var data = new Dictionary<int, Dictionary<bool, int>>();

Solution 4

Just in case anyone is here recently, an example of how to do this the quick and dirty way in .Net 4.0, as described by one of the commenters.

class Program
{
  static void Main(string[] args)
  {
     var twoDic = new Dictionary<Tuple<int, bool>, String>();
     twoDic.Add(new Tuple<int, bool>(3, true), "3 and true." );
     twoDic.Add(new Tuple<int, bool>(4, true), "4 and true." );
     twoDic.Add(new Tuple<int, bool>(3, false), "3 and false.");

     // Will throw exception. Item with the same key already exists.
     // twoDic.Add(new Tuple<int, bool>(3, true), "3 and true." );

     Console.WriteLine(twoDic[new Tuple<int, bool>(3,false)]);
     Console.WriteLine(twoDic[new Tuple<int, bool>(4,true)]);
     // Outputs "3 and false." and "4 and true."
  }
}

Solution 5

You need a key class for the Dictonary that implements GetHashCode correctly. And you can extend Dictonary to let you access it in a friendly way.

The KeyPair class:

public class KeyPair<Tkey1, Tkey2>
{
    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<Tkey1, Tkey2> o = obj as KeyPair<Tkey1, Tkey2>;
        if (o == null)
            return false;
        else
            return Key1.Equals(o.Key1) && Key2.Equals(o.Key2);
    }
}

Extend Dictonary<>:

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

You can use it like this:

KeyPairDictonary<int, bool, string> dict = 
    new KeyPairDictonary<int, bool, string>();

dict[1, false] = "test";
string test = dict[1, false];
Share:
87,200
Scott Schulthess
Author by

Scott Schulthess

Updated on July 05, 2022

Comments

  • Scott Schulthess
    Scott Schulthess almost 2 years

    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

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

    This is what I've been trying...hasn't worked

    Hashtable test = new Hashtable();
    test.Add(new Dictionary<int, bool>() { { 1, true } }, 555);
    Dictionary<int, bool> temp = new Dictionary<int, bool>() {{1, true}};
    string testz = test[temp].ToString(); 
    
  • David M
    David M about 15 years
    Don't forget you need to override GetHashCode and Equals to use this in a Hashtable.
  • JaredPar
    JaredPar about 15 years
    @David, not in this case. The default implementation of Equals will just perform equality on all of the fields which will work in this case. GetHashcode may not be as effiecient as the user would like but it will also function with the default implementation.
  • JaredPar
    JaredPar about 15 years
    @David, that being said, it's usually good practice to actually do so.
  • Paul Ruane
    Paul Ruane about 15 years
    The two parts of the key. In the original post, this was the integer 1 and the boolean false, hence I have concatenated these two in the sample code. (The delimiter is not strictly necessary in this example.)
  • Adam Houldsworth
    Adam Houldsworth over 11 years
    @JaredPar Doing so would be worthwhile. We recently uncovered a performance issue with the default implementation of GetHashCode for struct types. Manual implementation completely removed this bottleneck. Also, though a different solution, we find that the "dictionary within a dictionary" approach operates faster on all common actions (Dictionary<int, Dictionary<string, object>>). However, this doesn't allow for null portions of the composite key, whereas a struct/class key as above could easily allow for nulls without any extra legwork.
  • bacar
    bacar almost 11 years
    This works, but as @Adam mentions it is worth bearing in mind that the performance of struct's default GetHashCode might be terrible (or it might be just fine). Either way it will be correct (i.e. consistent with Equals()). Some structs, such as KeyValuePair<ushort, uint>, appear to use no fields from the struct at all and always return the same hash value - such keys would give you O(n) Dictionary lookup. Just be aware that struct isn't a panacea and be aware that you may eventually have to implement your own hashcode.
  • JaredPar
    JaredPar almost 11 years
    @bacar i didn't feel your comments were unhelpful at all. It is important to understand that this will create hash codes in a predetermined way that may very well be terrible for your application. If profiling showed this was type was causing a lot of bucket collisions that would absolutely be the correct place to go
  • Eamon Nerbonne
    Eamon Nerbonne almost 10 years
    Because the default implementations of GetHashCode & Equals are so slow and prone to hash-collisions, I've implemented ValueUtils which uses runtime code generation to efficiently implement those for you - that makes this approach a lot more palatable (in particular it addresses @bacar's problems).
  • pogosama
    pogosama almost 6 years
    Note: To use this on an older .NET Target Framework, an additional nuget package is needed.
  • Parrhesia Joe
    Parrhesia Joe over 5 years
    This has some advantages. It is clearer in use citiesByState["wa"]["seattle"] is beautiful code. Note, though, it has to do two hash compares and isEquals per lookup. A single dictionary with a composite (tuple) key may be significantly faster, when performance is critical. I generally prefer the form in this answer because of the elegance, but 7.0 makes tuples a lot less ugly.
  • Keith Stein
    Keith Stein almost 4 years
    Note: This also works in VB.NET as of Visual Basic 2017 (15.x)