using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; namespace Stackoverflow.Utilities { /// /// This is a dictionary guaranteed to have only one of each value and key. /// It may be searched either by TFirst or by TSecond, giving a unique answer because it is 1 to 1. /// It implements garbage-collector-friendly IEnumerable. /// /// From https://stackoverflow.com/a/35949314/1460422 /// The type of the "key" /// The type of the "value" public class BiDictionary : IEnumerable.Pair> { public struct Pair { public TFirst First; public TSecond Second; } public struct Enumerator : IEnumerator, IEnumerator { public Enumerator(IEnumerable> dictEnumerator) { _dictEnumerator = dictEnumerator.GetEnumerator(); } public Pair Current { get { Pair pair; pair.First = _dictEnumerator.Current.Key; pair.Second = _dictEnumerator.Current.Value; return pair; } } object IEnumerator.Current { get { return Current; } } public void Dispose() { _dictEnumerator.Dispose(); } public bool MoveNext() { return _dictEnumerator.MoveNext(); } public void Reset() { throw new NotSupportedException(); } private IEnumerator> _dictEnumerator; } #region Exception throwing methods /// /// Tries to add the pair to the dictionary. /// Throws an exception if either element is already in the dictionary /// /// /// public void Add(TFirst first, TSecond second) { if (_firstToSecond.ContainsKey(first) || _secondToFirst.ContainsKey(second)) throw new ArgumentException("Duplicate first or second"); _firstToSecond.TryAdd(first, second); _secondToFirst.TryAdd(second, first); } /// /// Find the TSecond corresponding to the TFirst first /// Throws an exception if first is not in the dictionary. /// /// the key to search for /// the value corresponding to first public TSecond GetByFirst(TFirst first) { TSecond second; if (!_firstToSecond.TryGetValue(first, out second)) throw new ArgumentException("first"); return second; } /// /// Find the TFirst corresponing to the Second second. /// Throws an exception if second is not in the dictionary. /// /// the key to search for /// the value corresponding to second public TFirst GetBySecond(TSecond second) { TFirst first; if (!_secondToFirst.TryGetValue(second, out first)) throw new ArgumentException("second"); return first; } /// /// Remove the record containing first. /// If first is not in the dictionary, throws an Exception. /// /// the key of the record to delete public void RemoveByFirst(TFirst first) { TSecond second; if (!_firstToSecond.TryGetValue(first, out second)) throw new ArgumentException("first"); _firstToSecond.TryRemove(first, out TSecond noop); _secondToFirst.TryRemove(second, out TFirst noopAgain); } /// /// Remove the record containing second. /// If second is not in the dictionary, throws an Exception. /// /// the key of the record to delete public void RemoveBySecond(TSecond second) { TFirst first; if (!_secondToFirst.TryGetValue(second, out first)) throw new ArgumentException("second"); _secondToFirst.TryRemove(second, out TFirst noop); _firstToSecond.TryRemove(first, out TSecond noopAgain); } #endregion #region Try methods /// /// Tries to add the pair to the dictionary. /// Returns false if either element is already in the dictionary /// /// /// /// true if successfully added, false if either element are already in the dictionary public bool TryAdd(TFirst first, TSecond second) { if (_firstToSecond.ContainsKey(first) || _secondToFirst.ContainsKey(second)) return false; bool result = _firstToSecond.TryAdd(first, second); result = _secondToFirst.TryAdd(second, first) || result; return result; } /// /// Find the TSecond corresponding to the TFirst first. /// Returns false if first is not in the dictionary. /// /// the key to search for /// the corresponding value /// true if first is in the dictionary, false otherwise public bool TryGetByFirst(TFirst first, out TSecond second) { return _firstToSecond.TryGetValue(first, out second); } /// /// Find the TFirst corresponding to the TSecond second. /// Returns false if second is not in the dictionary. /// /// the key to search for /// the corresponding value /// true if second is in the dictionary, false otherwise public bool TryGetBySecond(TSecond second, out TFirst first) { return _secondToFirst.TryGetValue(second, out first); } /// /// Remove the record containing first, if there is one. /// /// /// If first is not in the dictionary, returns false, otherwise true public bool TryRemoveByFirst(TFirst first) { TSecond second; if (!_firstToSecond.TryGetValue(first, out second)) return false; bool result = _firstToSecond.TryRemove(first, out TSecond noop); result = _secondToFirst.TryRemove(second, out TFirst noopAgain) || result; return result; } /// /// Remove the record containing second, if there is one. /// /// /// If second is not in the dictionary, returns false, otherwise true public bool TryRemoveBySecond(TSecond second) { TFirst first; if (!_secondToFirst.TryGetValue(second, out first)) return false; _secondToFirst.TryRemove(second, out TFirst noop); _firstToSecond.TryRemove(first, out TSecond noopAgain); return true; } #endregion /// /// The number of pairs stored in the dictionary /// public Int32 Count { get { return _firstToSecond.Count; } } /// /// Removes all items from the dictionary. /// public void Clear() { _firstToSecond.Clear(); _secondToFirst.Clear(); } public Enumerator GetEnumerator() { //enumerator.Reset(firstToSecond.GetEnumerator()); return new Enumerator(_firstToSecond); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private ConcurrentDictionary _firstToSecond = new ConcurrentDictionary(); private ConcurrentDictionary _secondToFirst = new ConcurrentDictionary(); } }