views:

4475

answers:

14

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();
+2  A: 

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

var data = new Dictionary<int, Dictionary<bool, int>>();
Jason Punyon
Backwards. Dictionary<bool, int> is the key type.
Sean Bright
@Sean Bright: I don't think that makes sense...
Jason Punyon
@Jason Punyon: based on the OPs question, it is closer to what he was asking for than what you provided. He mentions "using a two-dimensional typed key" which, last time I checked, didn't apply to an int.
Sean Bright
@Sean Bright: The retrieval syntax he's looking for in the first code snippet is what my answer provides. The second code snippet shows how his implementation of your solution doesn't provide what he wants. Using a dictionary as the key doesn't give the two dimensional typed key he's looking for.
Jason Punyon
Ah. I see now. My mistake.
Sean Bright
+1  A: 

I would suggest that you create a small custom class exposing the bool and int properties, and override its GetHashCode and Equals methods, then use this as the key.

David M
A: 

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.

Mike
+10  A: 

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.

JaredPar
Don't forget you need to override GetHashCode and Equals to use this in a Hashtable.
David M
@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
@David, that being said, it's usually good practice to actually do so.
JaredPar
+6  A: 

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...
    }
}
jachymko
Interesting solution.
OregonGhost
A: 

Could you use a Dictionary<KeyValuePair<int,bool>,int>?

Andrew Hare
A: 

Wrap your two-dimensional key in a separate type and use that type as a key. Also consider overriding GetHashCode() and Equals() methods. Preferably use Dictionary<> instead of HashTable since apparently you can use that.

bruno conde
A: 

Here is an example, you can use a normal Hashtable instead of the one I used.

leppie
A: 

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.

Paul Ruane
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.)
Paul Ruane
+1  A: 

you need a key class for the dictonary that implements gethashcode correctly. And you can extend dictonary to let you access it 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;
        }
    }
}

and you use it like this

        KeyPairDictonary<int, bool, string> dict = 
            new KeyPairDictonary<int, bool, string>();
        dict[1, false] = "test";
        string test = dict[1, false];
AndreasN
+1  A: 

I'd suggest a slight variation on jachymko's solution which will allow you to avoid creating a class for key pairs. Instead, wrap a private dictionary of dictionaries, as so:

public class MultiDictionary<K1, K2, V>
{
    private Dictionary<K1, Dictionary<K2, V>> dict = 
        new Dictionary<K1, Dictionary<K2, V>>();

    public V this[K1 key1, K2 key2]
    {
        get
        {
            return dict[key1][key2];
        }

        set
        {
            if (!dict.ContainsKey(key1))
            {
                dict[key1] = new Dictionary<K2, V>();
            }
            dict[key1][key2] = value;
        }
    }
}
Why? I thought about this for a second, but it seems to me worse because it has greater overhead (more GC pressure, worse locality) and there's no benefit to it - searching in a hash table has constant time-efficiency in average, so there's no point in doing it twice.
jachymko
Not for any efficiency reasons, but because I think the code is simpler (look at what you had to elide). If efficiency is a prime concern, then I agree that your original solution is better. I dislike having types like Pair<T,U> around, especially when .NET 4.0 will include tuples out-of-the-box.
A: 

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: 

@micromarketing: nice.

Eliza
A: 

Essentially you need to use an embedded hashtable. Because if you think about your question. A hashtable with two keys is a function with two independent variables, and f(x,y) is 2 dimentional 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: -You want to iterate over it so you need to overwrite the GetEnumerator() method. And you need your own Iterator that will iterate correctly in 2 dimensions. (Included Below) -You need to do more checking to be sure that there are no duplicates. (Included Below)

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 { /// /// 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
    }

}

}

Jim