Tuesday, July 24, 2007

BidirectionalDictionary

Common C# problem: you need a two-way dictionary. In many cases you need to set up a 1-1 mapping. Say you have an enumeration where each value has a corresponding string, and you need to be able to go from the enum value to the string, and vice versa. Wouldn't it be nice if this were handled by one object?

So, today I wrote the BidirectionalDictionary. It wraps two dictionaries (yay generics!). It's not that full-featured, but I put it together in 30 minutes, and it's solid code.

I decided to still label a "key" and "value", because in most cases you are still thinking in terms of a Dictionary, just one you can "reverse" back to the key.

Sample Usage

BidirectionalDict<int, string> map = new BidirectionalDict<int, string>
(
 new int[] { 1, 2, 3, 4 },
 new string[] { "a", "b", "c", "d" }
);
// Wasn't that syntax beautiful? (Much better than .Add, .Add ...)

map.Add(5, "e");
Console.WriteLine(map[1]);
Console.WriteLine(map.ValueToKey("d"));

Dictionary<int, string> a = new Dictionary<int, string>();
foreach(int key in map.Keys())
 Console.WriteLine(key);

foreach (KeyValuePair<int, string> pair in map.Pairs())
 Console.WriteLine(pair.Key.ToString() + "," + pair.Value);

Inspired by Python, I also added a TryKeyToValue(key, defaultValue) that lets you specify a default value if the key is not in the dict.

Note that there might be a slight performance benefit because the dictionaries can be initialized with a length.

Code

class BidirectionalDict<typeKey, typeValue>
{
 private Dictionary<typeKey, typeValue> m_dictKeyToValue;
 private Dictionary<typeValue, typeKey> m_dictValueToKey;

 public BidirectionalDict(typeKey[] arKeys, typeValue[] arValues)
 {
  if (arKeys.Length != arValues.Length)
   throw new FormatException();

  int nLength = arKeys.Length;
  m_dictKeyToValue = new Dictionary<typeKey, typeValue>(nLength);
  m_dictValueToKey = new Dictionary<typeValue, typeKey>(nLength);

  for (int i = 0; i < nLength; i++)
  {
   m_dictKeyToValue.Add(arKeys[i], arValues[i]);
   m_dictValueToKey.Add(arValues[i], arKeys[i]);
  }
 }
 public BidirectionalDict(KeyValuePair<typeKey, typeValue>[] pairs)
 {
  int nLength = pairs.Length;
  m_dictKeyToValue = new Dictionary<typeKey, typeValue>(nLength);
  m_dictValueToKey = new Dictionary<typeValue, typeKey>(nLength);
  foreach (KeyValuePair<typeKey, typeValue> pair in pairs)
  {
   m_dictKeyToValue.Add(pair.Key, pair.Value);
   m_dictValueToKey.Add(pair.Value, pair.Key);
  }
 }
 public void Add(typeKey key, typeValue value)
 {
  m_dictKeyToValue.Add(key, value);
  m_dictValueToKey.Add(value, key);
 }

 public typeValue this[typeKey index]
 {
  get {return m_dictKeyToValue[index]; }
 }
 public typeKey ValueToKey(typeValue value)
 {
  return m_dictValueToKey[value];
 }
 public bool ContainsKey(typeKey key)
 {
  return m_dictKeyToValue.ContainsKey(key);
 }
 public bool ContainsValue(typeValue value)
 {
  return m_dictValueToKey.ContainsKey(value);
 }
 public typeValue TryKeyToValue(typeKey key, typeValue defaultValue)
 {
  typeValue result;
  return m_dictKeyToValue.TryGetValue(key, out result) ? result : defaultValue;
 }
 public typeKey TryValueToKey(typeValue value, typeKey defaultKey)
 {
  typeKey result;
  return m_dictValueToKey.TryGetValue(value, out result) ? result : defaultKey;
 }

 public IEnumerable<typeKey> Keys()
 {
  foreach (typeKey key in m_dictKeyToValue.Keys)
   yield return key;
 }
 public IEnumerable<typeValue> Values()
 {
  foreach (typeValue value in m_dictValueToKey.Keys)
   yield return value;
 }
 public IEnumerable<KeyValuePair<typeKey, typeValue>> Pairs()
 {
  foreach (KeyValuePair<typeKey, typeValue> pair in m_dictKeyToValue)
         yield return pair;
 }
}

Got to go! Comments and criticism welcome.

No comments: