132

I want to store words in a dictionary in following way:

I can get word code by word: dict["SomeWord"] -> 123 and get word by word code: dict[123] -> "SomeWord"

Is it real? Of course one way to do it is two dictionaries: Dictionary<string,int> and Dictionary<int,string> but is there another way?

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Neir0
  • 12,849
  • 28
  • 83
  • 139
  • 2
    There is no standard (as of .NET 4) data-type that provides O(1) access both ways... AFAIK :) –  Jun 10 '12 at 04:29
  • Also not that a bidirectional-map (keyword?) imposes additional restrictions, unless a multi-bidirectional-map ... –  Jun 10 '12 at 04:37
  • 2
    http://stackoverflow.com/questions/1227683/bi-directional-dictionary , http://stackoverflow.com/questions/268321/bidirectional-1-to-1-dictionary-in-c-sharp –  Jun 10 '12 at 04:37

15 Answers15

137

I wrote a quick couple of classes that lets you do what you want. You'd probably need to extend it with more features, but it is a good starting point.

The use of the code looks like this:

var map = new Map<int, string>();

map.Add(42, "Hello");

Console.WriteLine(map.Forward[42]);
// Outputs "Hello"

Console.WriteLine(map.Reverse["Hello"]);
//Outputs 42

Here's the definition:

public class Map<T1, T2>
{
    private Dictionary<T1, T2> _forward = new Dictionary<T1, T2>();
    private Dictionary<T2, T1> _reverse = new Dictionary<T2, T1>();

    public Map()
    {
        this.Forward = new Indexer<T1, T2>(_forward);
        this.Reverse = new Indexer<T2, T1>(_reverse);
    }

    public class Indexer<T3, T4>
    {
        private Dictionary<T3, T4> _dictionary;
        public Indexer(Dictionary<T3, T4> dictionary)
        {
            _dictionary = dictionary;
        }
        public T4 this[T3 index]
        {
            get { return _dictionary[index]; }
            set { _dictionary[index] = value; }
        }
    }

    public void Add(T1 t1, T2 t2)
    {
        _forward.Add(t1, t2);
        _reverse.Add(t2, t1);
    }

    public Indexer<T1, T2> Forward { get; private set; }
    public Indexer<T2, T1> Reverse { get; private set; }
}
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Nice, .NET still does not have their "map" solution? – Pedro77 Jul 25 '13 at 13:36
  • 2
    @Pedro77 - It does now. ;-) – Enigmativity Jul 26 '13 at 00:17
  • 1
    So, you didn't like my edit. Can you share which us witch is the new .NET class? – Pedro77 Jul 26 '13 at 11:24
  • 3
    @Pedro77 - I was just being cheeky by suggesting that my class was the new "map" solution. – Enigmativity Jul 27 '13 at 01:32
  • I've used this as the starting point for a generic Map class in my [NTweaks library](https://www.nuget.org/packages/NTweaks). – Jon Egerton Dec 03 '13 at 16:49
  • 1
    can you please explain why the Indexer class is needed? Why not just have the two dictionaries alone in the map? Forward could use one of the dictionaries and reverse the other and we would be done...am i missing something here? – Aadith Ramia Dec 11 '13 at 13:03
  • 2
    @Aadith - The `Indexer<,>` class is there purely to allow the usage syntax to be `map.Forward[42]` rather than `map.Forward(42)`. I was trying to extend the normal `Dictionary<,>` class so I wanted to retain as much of the syntax as possible. – Enigmativity Dec 11 '13 at 22:18
  • 16
    This doesn't maintain class invariants on exceptions. It's possible for `_forward.Add` to succeed, and `_reverse.Add` to fail, leaving you with a partially added pair. –  May 14 '14 at 10:51
  • 5
    @hvd - Like I said - it's a quickly put together class. – Enigmativity May 14 '14 at 12:10
  • @hvd stated "This doesn't maintain class invariants on exceptions. " Can someone explain what this meant? – Nicholas Petersen Feb 06 '15 at 18:07
  • 4
    @NicholasPetersen A class invariant is a property of a class that must always be true for all valid instances of the class, that may be assumed to be true by all code using the class, and that all code using the class must leave intact. A useful class invariant here would be "for each pair (k, v) in _forward, there exists a corresponding pair (v, k) in _reverse", which calling code will definitely be relying on. I gave a brief example of how the methods in this class could cause that invariant to be violated. –  Feb 06 '15 at 18:13
  • Ahh, I see what you meant now, thanks. The solution in this case could be to simply check if the key is already added before adding. Or... an overkill in my opinion is wrap in try catch to make sure to catch any exceptions, but again a simple solution within the Add function. – Nicholas Petersen Feb 06 '15 at 18:15
  • 1
    @NicholasPetersen Actually, `try`...`catch` is probably exactly what I would do here, personally, for readability: it immediately makes it clear to the reader what happens in the common case when there is no error, and it makes it clear to the reader that there cannot be other edge cases that you might have forgot to handle. –  Feb 06 '15 at 18:22
  • 3
    You probably don't want a setter in the `Indexer` class, which will break the forward/reverse lookup. – Jeroen van Langen Nov 13 '17 at 20:53
  • @JeroenvanLangen - How so? – Enigmativity Nov 13 '17 at 21:49
  • 2
    If you write `myMap.Forward[index] = myValue;` only one dictionary will be updated. The reverse `myMap.Reverse[myValue] == index;` will be false, because the reverse doesn't have the value; You could fix it passing both directionaries to the indexer (in different order) and update both on the setter. – Jeroen van Langen Nov 14 '17 at 08:13
  • I think the issue explained by hvd can be fixed with changing `Add` code to `try { _forward.Add(t1, t2); } catch { return; } try { _reverse.Add(t2, t1); } catch { _forward.Remove(t1); }`. class might need a remove too – AaA Dec 13 '17 at 10:19
  • @JeroenvanLangen, I think private setter cannot be called to modify the dictionaries, unless you are using refactoring – AaA Dec 13 '17 at 10:20
  • 4
    @AaA It is not modifying the `Forward` dictionary property it self (which has `private set;`), but it is modifying the value on that dictionary thru the Indexer property of the Indexer class which passes it to the dictionary. `public T4 this[T3 index] { get { return _dictionary[index]; } set { _dictionary[index] = value; } }` So that's breaking the forward/reverse lookup. – Jeroen van Langen Dec 13 '17 at 10:49
  • 1
    There is an expanded version of this answer available as a nuget package https://www.nuget.org/packages/BidirectionalMap/ – farlee2121 Apr 24 '20 at 14:18
47

Regrettably, you need two dictionaries, one for each direction. However, you can easily get the inverse dictionary using LINQ:

Dictionary<T1, T2> dict = new Dictionary<T1, T2>();
Dictionary<T2, T1> dictInverse = dict.ToDictionary((i) => i.Value, (i) => i.Key);
Hasan Baidoun
  • 1,095
  • 9
  • 18
  • 1
    Although convenient, it would not perform very well if you are regularly accessing the inverse dictionary . It's expensive to create a new dictionary on-the-fly every time you need it. – Tom Bogle Oct 21 '21 at 11:33
  • @TomBogle Just a small detail: this would not actually be more expensive to access if the dictionary is not going to change. We need to update/regenerate dictInverse everytime we modify dict, which would be the expensive part. – Dani Barca Casafont Feb 28 '22 at 15:50
16

Expanded on Enigmativity code by adding initializes and Contains method.

public class Map<T1, T2> : IEnumerable<KeyValuePair<T1, T2>>
{
    private readonly Dictionary<T1, T2> _forward = new Dictionary<T1, T2>();
    private readonly Dictionary<T2, T1> _reverse = new Dictionary<T2, T1>();

    public Map()
    {
        Forward = new Indexer<T1, T2>(_forward);
        Reverse = new Indexer<T2, T1>(_reverse);
    }

    public Indexer<T1, T2> Forward { get; private set; }
    public Indexer<T2, T1> Reverse { get; private set; }

    public void Add(T1 t1, T2 t2)
    {
        _forward.Add(t1, t2);
        _reverse.Add(t2, t1);
    }

    public void Remove(T1 t1)
    {
        T2 revKey = Forward[t1];
        _forward.Remove(t1);
        _reverse.Remove(revKey);
    }
    
    public void Remove(T2 t2)
    {
        T1 forwardKey = Reverse[t2];
        _reverse.Remove(t2);
        _forward.Remove(forwardKey);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public IEnumerator<KeyValuePair<T1, T2>> GetEnumerator()
    {
        return _forward.GetEnumerator();
    }

    public class Indexer<T3, T4>
    {
        private readonly Dictionary<T3, T4> _dictionary;

        public Indexer(Dictionary<T3, T4> dictionary)
        {
            _dictionary = dictionary;
        }

        public T4 this[T3 index]
        {
            get { return _dictionary[index]; }
            set { _dictionary[index] = value; }
        }

        public bool Contains(T3 key)
        {
            return _dictionary.ContainsKey(key);
        }
    }
}

Here is a use case, check valid parentheses

public static class ValidParenthesisExt
{
    private static readonly Map<char, char>
        _parenthesis = new Map<char, char>
        {
            {'(', ')'},
            {'{', '}'},
            {'[', ']'}
        };

    public static bool IsValidParenthesis(this string input)
    {
        var stack = new Stack<char>();
        foreach (var c in input)
        {
            if (_parenthesis.Forward.Contains(c))
                stack.Push(c);
            else
            {
                if (stack.Count == 0) return false;
                if (_parenthesis.Reverse[c] != stack.Pop())
                    return false;
            }
        }
        return stack.Count == 0;
    }
}
ScottishTapWater
  • 3,656
  • 4
  • 38
  • 81
Xavier John
  • 8,474
  • 3
  • 37
  • 51
  • ```public T4 this[T3 index] { get { return _dictionary[index]; } set { _dictionary[index] = value; } }``` Due to this code, dictionary can point value out of the pair – Aditya Nadig Jan 27 '21 at 12:29
  • ex:``` var parent = parentMap.Reverse[pair.Key]; parentMap.Forward[parent] = ;``` – Aditya Nadig Jan 27 '21 at 12:32
9

You could use two dictionaries, as others have said, but note also that if both TKey and TValue are the of same type (and their runtime value domains are known to be disjoint) then you can just use the same dictionary by creating two entries for each key/value pairing:

dict["SomeWord"]= "123" and dict["123"]="SomeWord"

This way a single dictionary can be used for either type of lookup.

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
zmbq
  • 38,013
  • 14
  • 101
  • 171
  • 3
    Yes, this approach was acknowledged in the question :) –  Jun 10 '12 at 04:32
  • 4
    This ignores the possibility of the same value exists in both "keys" and "values". then it will conflict in this solution. – user1028741 Jul 21 '15 at 11:00
  • 1
    @user1028741 Agreed, although from the example it appears that they meant "of a different type" not "of the same type" – Hutch Sep 15 '17 at 19:48
  • This can result in unexpected results in the future then code goes through refactoring. E.g. then left and right sides starts to overlap. It adds close to nothing in performance. – Vinigas Jan 05 '21 at 12:22
7

What the heck, I'll throw my version into the mix:

public class BijectiveDictionary<TKey, TValue> 
{
    private EqualityComparer<TKey> _keyComparer;
    private Dictionary<TKey, ISet<TValue>> _forwardLookup;
    private EqualityComparer<TValue> _valueComparer;
    private Dictionary<TValue, ISet<TKey>> _reverseLookup;             

    public BijectiveDictionary()
        : this(EqualityComparer<TKey>.Default, EqualityComparer<TValue>.Default)
    {
    }

    public BijectiveDictionary(EqualityComparer<TKey> keyComparer, EqualityComparer<TValue> valueComparer)
        : this(0, EqualityComparer<TKey>.Default, EqualityComparer<TValue>.Default)
    {
    }

    public BijectiveDictionary(int capacity, EqualityComparer<TKey> keyComparer, EqualityComparer<TValue> valueComparer)
    {
        _keyComparer = keyComparer;
        _forwardLookup = new Dictionary<TKey, ISet<TValue>>(capacity, keyComparer);            
        _valueComparer = valueComparer;
        _reverseLookup = new Dictionary<TValue, ISet<TKey>>(capacity, valueComparer);            
    }

    public void Add(TKey key, TValue value)
    {
        AddForward(key, value);
        AddReverse(key, value);
    }

    public void AddForward(TKey key, TValue value)
    {
        ISet<TValue> values;
        if (!_forwardLookup.TryGetValue(key, out values))
        {
            values = new HashSet<TValue>(_valueComparer);
            _forwardLookup.Add(key, values);
        }
        values.Add(value);
    }

    public void AddReverse(TKey key, TValue value) 
    {
        ISet<TKey> keys;
        if (!_reverseLookup.TryGetValue(value, out keys))
        {
            keys = new HashSet<TKey>(_keyComparer);
            _reverseLookup.Add(value, keys);
        }
        keys.Add(key);
    }

    public bool TryGetReverse(TValue value, out ISet<TKey> keys)
    {
        return _reverseLookup.TryGetValue(value, out keys);
    }

    public ISet<TKey> GetReverse(TValue value)
    {
        ISet<TKey> keys;
        TryGetReverse(value, out keys);
        return keys;
    }

    public bool ContainsForward(TKey key)
    {
        return _forwardLookup.ContainsKey(key);
    }

    public bool TryGetForward(TKey key, out ISet<TValue> values)
    {
        return _forwardLookup.TryGetValue(key, out values);
    }

    public ISet<TValue> GetForward(TKey key)
    {
        ISet<TValue> values;
        TryGetForward(key, out values);
        return values;
    }

    public bool ContainsReverse(TValue value)
    {
        return _reverseLookup.ContainsKey(value);
    }

    public void Clear()
    {
        _forwardLookup.Clear();
        _reverseLookup.Clear();
    }
}

Add some data to it:

var lookup = new BijectiveDictionary<int, int>();

lookup.Add(1, 2);
lookup.Add(1, 3);
lookup.Add(1, 4);
lookup.Add(1, 5);

lookup.Add(6, 2);
lookup.Add(6, 8);
lookup.Add(6, 9);
lookup.Add(6, 10);

And then do the lookup:

lookup[2] --> 1, 6
lookup[3] --> 1
lookup[8] --> 6
Ostati
  • 4,623
  • 3
  • 44
  • 48
6

You can use this extension method, although it uses enumeration, and thus may not be as performant for large data sets. If you are worried about efficiency, then you need two dictionaries. If you want to wrap the two dictionaries into one class, see the accepted answer for this question: Bidirectional 1 to 1 Dictionary in C#

public static class IDictionaryExtensions
{
    public static TKey FindKeyByValue<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TValue value)
    {
        if (dictionary == null)
            throw new ArgumentNullException("dictionary");

        foreach (KeyValuePair<TKey, TValue> pair in dictionary)
            if (value.Equals(pair.Value)) return pair.Key;

        throw new Exception("the value is not found in the dictionary");
    }
}
Community
  • 1
  • 1
moribvndvs
  • 42,191
  • 11
  • 135
  • 149
6

I made an expanded version of Enigmativity's answer available as a nuget package https://www.nuget.org/packages/BidirectionalMap/

It is open sourced here

farlee2121
  • 2,959
  • 4
  • 29
  • 41
4

A modified version of Xavier John's answer, with an additional constructor to take forward and reverse Comparers. This would support case-insensitive keys, for example. Further constructors could be added, if needed, to pass further arguments to the forward and reverse Dictionary constructors.

public class Map<T1, T2> : IEnumerable<KeyValuePair<T1, T2>>
{
    private readonly Dictionary<T1, T2> _forward;
    private readonly Dictionary<T2, T1> _reverse;

    /// <summary>
    /// Constructor that uses the default comparers for the keys in each direction.
    /// </summary>
    public Map()
        : this(null, null)
    {
    }

    /// <summary>
    /// Constructor that defines the comparers to use when comparing keys in each direction.
    /// </summary>
    /// <param name="t1Comparer">Comparer for the keys of type T1.</param>
    /// <param name="t2Comparer">Comparer for the keys of type T2.</param>
    /// <remarks>Pass null to use the default comparer.</remarks>
    public Map(IEqualityComparer<T1> t1Comparer, IEqualityComparer<T2> t2Comparer)
    {
        _forward = new Dictionary<T1, T2>(t1Comparer);
        _reverse = new Dictionary<T2, T1>(t2Comparer);
        Forward = new Indexer<T1, T2>(_forward);
        Reverse = new Indexer<T2, T1>(_reverse);
    }

    // Remainder is the same as Xavier John's answer:
    // https://stackoverflow.com/a/41907561/216440
    ...
}

Usage example, with a case-insensitive key:

Map<int, string> categories = 
new Map<int, string>(null, StringComparer.CurrentCultureIgnoreCase)
{
    { 1, "Bedroom Furniture" },
    { 2, "Dining Furniture" },
    { 3, "Outdoor Furniture" }, 
    { 4, "Kitchen Appliances" }
};

int categoryId = 3;
Console.WriteLine("Description for category ID {0}: '{1}'", 
    categoryId, categories.Forward[categoryId]);

string categoryDescription = "DINING FURNITURE";
Console.WriteLine("Category ID for description '{0}': {1}", 
    categoryDescription, categories.Reverse[categoryDescription]);

categoryDescription = "outdoor furniture";
Console.WriteLine("Category ID for description '{0}': {1}", 
    categoryDescription, categories.Reverse[categoryDescription]);

// Results:
/*
Description for category ID 3: 'Outdoor Furniture'
Category ID for description 'DINING FURNITURE': 2
Category ID for description 'outdoor furniture': 3
*/
Simon Elms
  • 17,832
  • 21
  • 87
  • 103
2

Here's my code. Everything is O(1) except for the seeded constructors.

using System.Collections.Generic;
using System.Linq;

public class TwoWayDictionary<T1, T2>
{
    Dictionary<T1, T2> _Forwards = new Dictionary<T1, T2>();
    Dictionary<T2, T1> _Backwards = new Dictionary<T2, T1>();

    public IReadOnlyDictionary<T1, T2> Forwards => _Forwards;
    public IReadOnlyDictionary<T2, T1> Backwards => _Backwards;

    public IEnumerable<T1> Set1 => Forwards.Keys;
    public IEnumerable<T2> Set2 => Backwards.Keys;


    public TwoWayDictionary()
    {
        _Forwards = new Dictionary<T1, T2>();
        _Backwards = new Dictionary<T2, T1>();
    }

    public TwoWayDictionary(int capacity)
    {
        _Forwards = new Dictionary<T1, T2>(capacity);
        _Backwards = new Dictionary<T2, T1>(capacity);
    }

    public TwoWayDictionary(Dictionary<T1, T2> initial)
    {
        _Forwards = initial;
        _Backwards = initial.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
    }

    public TwoWayDictionary(Dictionary<T2, T1> initial)
    {
        _Backwards = initial;
        _Forwards = initial.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
    }


    public T1 this[T2 index]
    {
        get => _Backwards[index];
        set
        {
            if (_Backwards.TryGetValue(index, out var removeThis))
                _Forwards.Remove(removeThis);

            _Backwards[index] = value;
            _Forwards[value] = index;
        }
    }

    public T2 this[T1 index]
    {
        get => _Forwards[index];
        set
        {
            if (_Forwards.TryGetValue(index, out var removeThis))
                _Backwards.Remove(removeThis);

            _Forwards[index] = value;
            _Backwards[value] = index;
        }
    }

    public int Count => _Forwards.Count;

    public bool Contains(T1 item) => _Forwards.ContainsKey(item);
    public bool Contains(T2 item) => _Backwards.ContainsKey(item);

    public bool Remove(T1 item)
    {
        if (!this.Contains(item))
            return false;

        var t2 = _Forwards[item];

        _Backwards.Remove(t2);
        _Forwards.Remove(item);

        return true;
    }

    public bool Remove(T2 item)
    {
        if (!this.Contains(item))
            return false;

        var t1 = _Backwards[item];

        _Forwards.Remove(t1);
        _Backwards.Remove(item);

        return true;
    }

    public void Clear()
    {
        _Forwards.Clear();
        _Backwards.Clear();
    }
}
Iamsodarncool
  • 663
  • 5
  • 9
  • 1
    I wonder how the constructor will behave if you pass it an existing dictionary where key and value types are the same. How will it resolve whether to use the backwards vs. the forwards ones? – Colm Bhandal Apr 05 '20 at 17:26
  • 1
    `error CS0121: The call is ambiguous between the following methods or properties: 'TwoWayDictionary.TwoWayDictionary(Dictionary)' and 'TwoWayDictionary.TwoWayDictionary(Dictionary)'` – Graham Oct 07 '21 at 11:07
1

Bictionary

Here is a commingling of what I liked in each answer. It implements IEnumerable so it can use collection initializer, as you can see in the example.

Usage Constraint:

  • You are using different datatypes. (i.e., T1T2)

Code:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        Bictionary<string, int> bictionary = 
            new Bictionary<string,int>() {
                { "a",1 }, 
                { "b",2 }, 
                { "c",3 } 
            };

        // test forward lookup
        Console.WriteLine(bictionary["b"]);
        // test forward lookup error
        //Console.WriteLine(bictionary["d"]);
        // test reverse lookup
        Console.WriteLine(bictionary[3]); 
        // test reverse lookup error (throws same error as forward lookup does)
        Console.WriteLine(bictionary[4]); 
    }
}

public class Bictionary<T1, T2> : Dictionary<T1, T2>
{
    public T1 this[T2 index]
    {
        get
        {
            if(!this.Any(x => x.Value.Equals(index)))
               throw new System.Collections.Generic.KeyNotFoundException();
            return this.First(x => x.Value.Equals(index)).Key;
        }
    }
}

Fiddle:

https://dotnetfiddle.net/mTNEuw

toddmo
  • 20,682
  • 14
  • 97
  • 107
  • Very elegant solution! Could you maybe explain the guts of it a little further? Am I right, that you can't make a `Bictionary` even if all strings are unique? – Marcus Mangelsdorf Oct 06 '15 at 11:20
  • @Merlin2001, That's correct. More precisely, you couldn't do forward lookups with that. I'll have to think about how to overcome that. It compiles but it always finds the reverse indexer first when `T1 == T2`, so forward lookups fail. Furthermore, I can't override the default indexer because then lookup calls would be ambiguous. I have added this constraint and removed the previous one, because values of `T1` can overlap with values of `T2`. – toddmo Oct 07 '15 at 22:50
  • 13
    There's a pretty serious performance problem on the reverse; the dictionary is searched twice, with an O(n) performance hit; it'd be much faster to use a second dictionary, and would remove the type constraint. – Steve Cooper May 15 '16 at 09:25
  • @SteveCooper, maybe I could get rid of performance hit by wrapping it in a `try` and converting exceptions to `KeyNotFoundExceptions`. – toddmo May 15 '16 at 15:20
  • 4
    @toddmo you might be able to double the speed this way. The larger problem is that both .First and .Any search one item at a time, testing each one. So to test a 1,000,000 item list takes 1,000,000 times longer to search than a 1-element list. Dictionaries are much faster and do not slow down as you add more items, so a second inverse dictionary will save epic amounts of time over large lists. It may not be relevant, but it is the sort of thing that can be fine with small amounts of data during testing, and then it murders performance on a real server with serious data. – Steve Cooper May 15 '16 at 20:37
1

This is an old issue but I wanted to add a two extension methods in case anyone finds it useful. The second is not as useful but it provides a starting point if one to one dictionaries need to be supported.

    public static Dictionary<VALUE,KEY> Inverse<KEY,VALUE>(this Dictionary<KEY,VALUE> dictionary)
    {
        if (dictionary==null || dictionary.Count == 0) { return null; }

        var result = new Dictionary<VALUE, KEY>(dictionary.Count);

        foreach(KeyValuePair<KEY,VALUE> entry in dictionary)
        {
            result.Add(entry.Value, entry.Key);
        }

        return result;
    }

    public static Dictionary<VALUE, KEY> SafeInverse<KEY, VALUE>(this Dictionary<KEY, VALUE> dictionary)
    {
        if (dictionary == null || dictionary.Count == 0) { return null; }

        var result = new Dictionary<VALUE, KEY>(dictionary.Count);

        foreach (KeyValuePair<KEY, VALUE> entry in dictionary)
        {
            if (result.ContainsKey(entry.Value)) { continue; }

            result.Add(entry.Value, entry.Key);
        }

        return result;
    }
Felipe Ramos
  • 305
  • 3
  • 6
1

Here's an alternative solution to those that were suggested. Removed the inner class and insured the coherence when adding/removing items

using System.Collections;
using System.Collections.Generic;

public class Map<E, F> : IEnumerable<KeyValuePair<E, F>>
{
    private readonly Dictionary<E, F> _left = new Dictionary<E, F>();
    public IReadOnlyDictionary<E, F> left => this._left;
    private readonly Dictionary<F, E> _right = new Dictionary<F, E>();
    public IReadOnlyDictionary<F, E> right => this._right;

    public void RemoveLeft(E e)
    {
        if (!this.left.ContainsKey(e)) return;
        this._right.Remove(this.left[e]);
        this._left.Remove(e);
    }

    public void RemoveRight(F f)
    {
        if (!this.right.ContainsKey(f)) return;
        this._left.Remove(this.right[f]);
        this._right.Remove(f);
    }

    public int Count()
    {
        return this.left.Count;
    }

    public void Set(E left, F right)
    {
        if (this.left.ContainsKey(left))
        {
            this.RemoveLeft(left);
        }
        if (this.right.ContainsKey(right))
        {
            this.RemoveRight(right);
        }
        this._left.Add(left, right);
        this._right.Add(right, left);
    }


    public IEnumerator<KeyValuePair<E, F>> GetEnumerator()
    {
        return this.left.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.left.GetEnumerator();
    }
}

sirpaddow
  • 11
  • 2
0

This uses an indexer for the reverse lookup.
The reverse lookup is O(n) but it also does not use two dictionaries

public sealed class DictionaryDoubleKeyed : Dictionary<UInt32, string>
{   // used UInt32 as the key as it has a perfect hash
    // if most of the lookup is by word then swap
    public void Add(UInt32 ID, string Word)
    {
        if (this.ContainsValue(Word)) throw new ArgumentException();
        base.Add(ID, Word);
    }
    public UInt32 this[string Word]
    {   // this will be O(n)
        get
        {
            return this.FirstOrDefault(x => x.Value == Word).Key;
        }
    } 
}
paparazzo
  • 44,497
  • 23
  • 105
  • 176
  • For example: possible NRE in `this[string Word]`. Additional problems are variable names not corresponding to common practices, comment inconsistent with code (`UInt16` vs `UInt32` - that's why: don't use comments!), solution is not generic, ... – BartoszKP Feb 20 '17 at 14:42
0

The following encapsulating class utilizes linq (IEnumerable Extensions) over 1 dictionary instance.

public class TwoWayDictionary<TKey, TValue>
{
    readonly IDictionary<TKey, TValue> dict;
    readonly Func<TKey, TValue> GetValueWhereKey;
    readonly Func<TValue, TKey> GetKeyWhereValue;
    readonly bool _mustValueBeUnique = true;

    public TwoWayDictionary()
    {
        this.dict = new Dictionary<TKey, TValue>();
        this.GetValueWhereKey = (strValue) => dict.Where(kvp => Object.Equals(kvp.Key, strValue)).Select(kvp => kvp.Value).FirstOrDefault();
        this.GetKeyWhereValue = (intValue) => dict.Where(kvp => Object.Equals(kvp.Value, intValue)).Select(kvp => kvp.Key).FirstOrDefault();
    }

    public TwoWayDictionary(KeyValuePair<TKey, TValue>[] kvps)
        : this()
    {
        this.AddRange(kvps);
    }

    public void AddRange(KeyValuePair<TKey, TValue>[] kvps)
    {
        kvps.ToList().ForEach( kvp => {        
            if (!_mustValueBeUnique || !this.dict.Any(item => Object.Equals(item.Value, kvp.Value)))
            {
                dict.Add(kvp.Key, kvp.Value);
            } else {
                throw new InvalidOperationException("Value must be unique");
            }
        });
    }

    public TValue this[TKey key]
    {
        get { return GetValueWhereKey(key); }
    }

    public TKey this[TValue value]
    {
        get { return GetKeyWhereValue(value); }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var dict = new TwoWayDictionary<string, int>(new KeyValuePair<string, int>[] {
            new KeyValuePair<string, int>(".jpeg",100),
            new KeyValuePair<string, int>(".jpg",101),
            new KeyValuePair<string, int>(".txt",102),
            new KeyValuePair<string, int>(".zip",103)
        });


        var r1 = dict[100];
        var r2 = dict[".jpg"];

    }

}
Brett Caswell
  • 732
  • 1
  • 4
  • 16
0

There is a BijectionDictionary type available in this open source repo:

https://github.com/ColmBhandal/CsharpExtras.

It isn't qualitatively much different to the other answers given. It uses two dictionaries, like most of those answers.

What is novel, I believe, about this dictionary vs. the other answers so far, is that rather than behaving like a two way dictionary, it just behaves like a one-way, familiar dictionary and then dynamically allows you to flip the dictionary using the Reverse property. The flipped object reference is shallow, so it will still be able to modify the same core object as the original reference. So you can have two references to the same object, except one of them is flipped.

Another thing that is probably unique about this dictionary is that there are some tests written for it in the test project under that repo. It's been used by us in practice and has been pretty stable so far.

Colm Bhandal
  • 3,343
  • 2
  • 18
  • 29