Code/Util/BiDictionary.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Nodebox.Util;
public class BiDictionary<TForwardKey, TReverseKey> : IEnumerable<KeyValuePair<TForwardKey, TReverseKey>> {
public Indexer<TForwardKey, TReverseKey> Forward { get; set; } = new();
public Indexer<TReverseKey, TForwardKey> Reverse { get; set; } = new();
const string DuplicateKeyErrorMessage = "";
public BiDictionary() { }
public BiDictionary(IDictionary<TForwardKey, TReverseKey> oneWayMap) {
Forward = new Indexer<TForwardKey, TReverseKey>(oneWayMap);
var reversedOneWayMap = oneWayMap.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
Reverse = new Indexer<TReverseKey, TForwardKey>(reversedOneWayMap);
}
public BiDictionary(IEqualityComparer<TForwardKey> forwardComparer, IEqualityComparer<TReverseKey> reverseComparer) {
Forward = new Indexer<TForwardKey, TReverseKey>(forwardComparer ?? EqualityComparer<TForwardKey>.Default);
Reverse = new Indexer<TReverseKey, TForwardKey>(reverseComparer ?? EqualityComparer<TReverseKey>.Default);
}
public BiDictionary(IDictionary<TForwardKey, TReverseKey> oneWayMap, IEqualityComparer<TForwardKey> forwardComparer, IEqualityComparer<TReverseKey> reverseComparer) {
Forward = new Indexer<TForwardKey, TReverseKey>(oneWayMap, forwardComparer ?? EqualityComparer<TForwardKey>.Default);
var reversedOneWayMap = oneWayMap.ToDictionary(kvp => kvp.Value, kvp => kvp.Key, reverseComparer ?? EqualityComparer<TReverseKey>.Default);
Reverse = new Indexer<TReverseKey, TForwardKey>(reversedOneWayMap, reverseComparer ?? EqualityComparer<TReverseKey>.Default);
}
public void Add(TForwardKey t1, TReverseKey t2) {
if (Forward.ContainsKey(t1)) {
throw new ArgumentException(DuplicateKeyErrorMessage, nameof(t1));
}
if (Reverse.ContainsKey(t2)) {
throw new ArgumentException(DuplicateKeyErrorMessage, nameof(t2));
}
Forward.Add(t1, t2);
Reverse.Add(t2, t1);
}
public bool TryAdd(TForwardKey t1, TReverseKey t2) {
if (!Forward.TryAdd(t1, t2)) {
return false;
}
if (!Reverse.TryAdd(t2, t1)) {
Forward.Remove(t1); // Rollback
return false;
}
return true;
}
public bool Remove(TForwardKey forwardKey) {
if (Forward.ContainsKey(forwardKey) == false) {
return false;
}
var reverseKey = Forward[forwardKey];
bool success;
if (Forward.Remove(forwardKey)) {
if (Reverse.Remove(reverseKey)) {
success = true;
} else {
Forward.Add(forwardKey, reverseKey);
success = false;
}
} else {
success = false;
}
return success;
}
public bool RemoveReverse(TReverseKey reverseKey) {
if (Reverse.ContainsKey(reverseKey) == false) {
return false;
}
var forwardKey = Reverse[reverseKey];
bool success;
if (Reverse.Remove(reverseKey)) {
if (Forward.Remove(forwardKey)) {
success = true;
} else {
Reverse.Add(reverseKey, forwardKey);
success = false;
}
} else {
success = false;
}
return success;
}
public int Count => Forward.Count;
IEnumerator<KeyValuePair<TForwardKey, TReverseKey>> IEnumerable<KeyValuePair<TForwardKey, TReverseKey>>.GetEnumerator() {
return Forward.GetEnumerator();
}
public IEnumerator GetEnumerator() {
return Forward.GetEnumerator();
}
/// <summary>
/// Publicly read-only lookup to prevent inconsistent state between forward and reverse map lookups
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
public class Indexer<TKey, TValue> : IReadOnlyDictionary<TKey, TValue> {
private readonly IDictionary<TKey, TValue> _dictionary;
public Indexer() {
_dictionary = new Dictionary<TKey, TValue>();
}
public Indexer(IDictionary<TKey, TValue> dictionary) {
_dictionary = dictionary;
}
public Indexer(IEqualityComparer<TKey> comparer) {
_dictionary = new Dictionary<TKey, TValue>(comparer);
}
public Indexer(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) {
_dictionary = new Dictionary<TKey, TValue>(dictionary, comparer ?? EqualityComparer<TKey>.Default);
}
public TValue this[TKey index] => _dictionary[index];
public int Count => _dictionary.Count;
public static implicit operator Dictionary<TKey, TValue>(Indexer<TKey, TValue> indexer) {
return new Dictionary<TKey, TValue>(indexer._dictionary);
}
internal void Add(TKey key, TValue value) {
_dictionary.Add(key, value);
}
internal bool TryAdd(TKey key, TValue value) {
if (_dictionary.ContainsKey(key)) {
return false;
}
_dictionary.Add(key, value);
return true;
}
internal bool Remove(TKey key) {
return _dictionary.Remove(key);
}
public bool ContainsKey(TKey key) {
return _dictionary.ContainsKey(key);
}
public bool TryGetValue(TKey key, out TValue value) {
return _dictionary.TryGetValue(key, out value);
}
public IEnumerable<TKey> Keys {
get {
return _dictionary.Keys;
}
}
public IEnumerable<TValue> Values {
get {
return _dictionary.Values;
}
}
/// <summary>
/// Deep copy lookup as a dictionary
/// </summary>
/// <returns></returns>
public Dictionary<TKey, TValue> ToDictionary() {
return new Dictionary<TKey, TValue>(_dictionary);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
return _dictionary.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return _dictionary.GetEnumerator();
}
}
}