views:

1499

answers:

7

Building on this question, is there a simple solution for having a multi-key dictionary where either key individually can be used to identify the value?

ie.

MultikeyDictionary<TKey1, TKey2, TValue> foo;
foo.Add(key1, key2, value);
myValue = foo[key1];
// value == myValue
foo.Remove(key2);
myValue = foo[key1]; // invalid, Exception or null returned
A: 

Sure, it's an OO language and you can implement whatever O's you want. You are going to have some ambiguity to resolve (what if TKey1 and TKey2 are the same type, which methods get called then?)

David B
I'm actually not sure how (or if) the C# compiler would resolve that... Interesting point.
Matthew Scharley
This is a compile-time error: Type 'Test<T1,T2>' already defines a member called 'x' with the same parameter types
Matthew Scharley
+3  A: 

This blog post seems to detail a decent implementation.

Noldorin
+1  A: 

There's nothing built into .NET BCL for this type of collection at the moment.

I see two options:

  1. Use a two-level dictionary. The first level maps different keys to some common unique key (let's say a GUID), and the second level maps the GUID to the actual value.

  2. Create a custom key class and implement Equals() and GetHashCode() so that any one component of the key is sufficient to find the entire key. You could then supply helper methods to construct instances of the key using only one of the values so that you could do lookups.

LBushkin
1 is a really smart idea. I thought about 2 but I think you'd reach a brick wall on GetHashCode. It would need to return the same value for {1,1}, {2,1}, {1,2} but NOT {2,2} (although if the original key WAS {2,2} it would need to.)
Ryan Brunner
GetHashCode doesn't need to return unique values, but it's values do need to agree with your Equals values. Your object could just return 0, save alot of calculation time, but waste alot when the collection tries to use your (very) naive hash code.
Matthew Scharley
@Ryan - I would implement GetHashCode() to just hash one of the values. Hash codes don't have to be unique (as Matthew identifies) - they just help the dictionary minimize collisions and thereby affect performance.
LBushkin
+1  A: 

Yes, define a class that adds the object to an internal hashtable with both keys,

 public MyClass<k1, k2, T>: Dictionary<object, T>
  {
      private Dictionary<k1, k2> keyMap;
      public new Add(k1 key1Val, k2 key2Val, T object)
      {
         keyMap.Add(key1Val, key2Val);
         base.Add(k2, object)
      }
      public Remove(k1 key1Val) 
      { 
          base.Remove(keyMap[key1Val]); 
          keyMap.Remove(key1Val);
      }
      public Remove(k2 key2Val) 
      { 
        base.Remove(key2Val);
        keyMap.Remove(key2Val);
      }
  }
Charles Bretana
Yes, precisely like this.
Adam Luter
(but as noted, you can't use the same types with this implementation).
Adam Luter
A: 

You won't be able to define the overloads for both types, and the generics system doesn't allow for an arbitrary number of types (like methods allow params). So, you'd be stuck with a set of classes which defined 2, 3, 4, etc. simultaneous keys. Additionally, you'd have to use object as the parameter for get and set, using runtime type checks to simulate the overload.

Additionally, you'd only store one dictionary of <TKEY1,VAL>, the other dictionaries would be of <TKEY2,TKEY1>, <TKEY3,TKEY1> and would act as indexes on the main dictionary.

It's mostly boiler plate code.

Adam Luter
I only ever need 2 key types. This isn't an issue.
Matthew Scharley
A: 

You may find my IndexMap implementation to be a good base for rewriting it from Java into C#. The programming model isn't as elegant as I'd prefer, but it isn't meant for developing with directly. Rather it lies behind a caching library which supplies standard annotations to allow for a succinct coding style. By using the Map interface it provides a clean compositional model when combining it with self-populating, expirational, and evictible map decorators. I am sure that someone could come up with a nice programming interface for direct usage where it is acceptable to lose the benefit of the Map interface.

Ben Manes
+1  A: 

Another simple (and effective) implementation would be to use PowerCollections' Pair<TFirst, TSecond> type as a dictionary key, something like

Dictionary<Pair<TKey1, TKey2>, TValue> foo;
foo.Add(new Pair<TKey1, TKey2>(key1, key2), value);

Pair<> implements Equals and GetHashCode consistently, so you don't need to resort to multi-level dictionaries (which are more cumbersome and probably less effective).

There's also a Triple<TFirst, TSecond, TThird> if you need a 3-key dictionary.

Igor Brejc