// // ImmutableDictionary.cs // // Contains code from ACIS P2P Library (https://github.com/ptony82/brunet) // // Author: // Mike Krüger // // Copyright (c) 2013 Xamarin Inc. (http://xamarin.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace Quobject.Collections.Immutable { /** Read-only immutable data structure for IComparable Keys * Implemented as a readonly binary AVL tree, so most operations * have 1.44 Log C complexity where C is the count. * * http://en.wikipedia.org/wiki/AVL_tree * To modify, use the InsertIntoNew and RemoveFromNew methods * which return a new instance with minimal changes (about Log C), * so this is an efficient way to make changes without having * to copy the entire data structure. * * Clearly this is a thread-safe class (because it is read-only), * but note: if the K or V types are not immutable, you could have * a problem: someone could modify the object without changing the * dictionary and not only would the Dictionary be incorrectly ordered * you could have race conditions. It is required that you only use * immutable key types in the dictionary, and only thread-safe if * both the keys and values are immutable. */ public class ImmutableDictionary : IImmutableDictionary where TKey : System.IComparable { internal static readonly ImmutableDictionary Empty = new ImmutableDictionary(); AvlNode> root = AvlNode>.Empty; readonly IEqualityComparer keyComparer; readonly IEqualityComparer valueComparer; internal ImmutableDictionary() { } internal ImmutableDictionary(AvlNode> root, IEqualityComparer keyComparer, IEqualityComparer valueComparer) { this.root = root; this.keyComparer = keyComparer; this.valueComparer = valueComparer; } public ImmutableDictionary WithComparers(IEqualityComparer keyComparer, IEqualityComparer valueComparer) { return new ImmutableDictionary(root, keyComparer, valueComparer); } public ImmutableDictionary WithComparers(IEqualityComparer keyComparer) { return WithComparers(keyComparer, valueComparer); } #region IImmutableDictionary implementation public ImmutableDictionary Add(TKey key, TValue value) { var pair = new KeyValuePair(key, value); return new ImmutableDictionary(root.InsertIntoNew(pair, CompareKV), keyComparer, valueComparer); } IImmutableDictionary IImmutableDictionary.Add(TKey key, TValue value) { return Add(key, value); } public ImmutableDictionary AddRange(IEnumerable> pairs) { var result = this; foreach (var kv in pairs) result = result.Add(kv.Key, kv.Value); return result; } IImmutableDictionary IImmutableDictionary.AddRange(IEnumerable> pairs) { return AddRange(pairs); } public ImmutableDictionary Clear() { return Empty; } IImmutableDictionary IImmutableDictionary.Clear() { return Clear(); } static int CompareKV(KeyValuePair left, KeyValuePair right) { return left.Key.CompareTo(right.Key); } public bool Contains(KeyValuePair kv) { var node = root.SearchNode(kv, CompareKV); return !node.IsEmpty && valueComparer.Equals(node.Value.Value, kv.Value); } public ImmutableDictionary Remove(TKey key) { bool found; var pair = new KeyValuePair(key, default (TValue)); return new ImmutableDictionary(root.RemoveFromNew(pair, CompareKV, out found), keyComparer, valueComparer); } IImmutableDictionary IImmutableDictionary.Remove(TKey key) { return Remove(key); } public IImmutableDictionary RemoveRange(IEnumerable keys) { IImmutableDictionary result = this; foreach (var key in keys) { result = result.Remove(key); } return result; } IImmutableDictionary IImmutableDictionary.RemoveRange(IEnumerable keys) { return RemoveRange(keys); } public ImmutableDictionary SetItem(TKey key, TValue value) { var result = this; if (result.ContainsKey(key)) result = result.Remove(key); return result.Add(key, value); } IImmutableDictionary IImmutableDictionary.SetItem(TKey key, TValue value) { return SetItem(key, value); } public IImmutableDictionary SetItems(IEnumerable> items) { var result = this; foreach (var kv in items) { result = result.SetItem(kv.Key, kv.Value); } return result; } IImmutableDictionary IImmutableDictionary.SetItems(IEnumerable> items) { return SetItems(items); } public IEqualityComparer KeyComparer { get { return keyComparer; } } public IEqualityComparer ValueComparer { get { return valueComparer; } } #endregion #region IReadOnlyDictionary implementation public bool ContainsKey(TKey key) { return !root.SearchNode(new KeyValuePair(key, default(TValue)), CompareKV).IsEmpty; } public bool TryGetValue(TKey key, out TValue value) { var node = root.SearchNode(new KeyValuePair(key, default(TValue)), CompareKV); if (node.IsEmpty) { value = default (TValue); return false; } value = node.Value.Value; return true; } public TValue this [TKey key] { get { TValue value; if (TryGetValue(key, out value)) return value; throw new KeyNotFoundException(String.Format("Key: {0}", key)); } } public IEnumerable Keys { get { foreach (var kv in this) { yield return kv.Key; } } } public IEnumerable Values { get { foreach (var kv in this) { yield return kv.Value; } } } #endregion #region IEnumerable implementation public IEnumerator> GetEnumerator() { var to_visit = new Stack>>(); to_visit.Push(root); while (to_visit.Count > 0) { var this_d = to_visit.Pop(); if (this_d.IsEmpty) { continue; } if (this_d.Left.IsEmpty) { //This is the next smallest value in the Dict: yield return this_d.Value; to_visit.Push(this_d.Right); } else { //Break it up to_visit.Push(this_d.Right); to_visit.Push(new AvlNode>(this_d.Value)); to_visit.Push(this_d.Left); } } } #endregion #region IEnumerable implementation IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region IReadOnlyCollection implementation public int Count { get { return root.Count; } } #endregion public Builder ToBuilder() { return new Builder(root, keyComparer, valueComparer); } public sealed class Builder : IDictionary { AvlNode> root; IEqualityComparer keyComparer; public IEqualityComparer KeyComparer { get { return keyComparer; } set { keyComparer = value; } } IEqualityComparer valueComparer; public IEqualityComparer ValueComparer { get { return valueComparer; } set { valueComparer = value; } } internal Builder(AvlNode> root, IEqualityComparer keyComparer, IEqualityComparer valueComparer) { this.root = root.ToMutable(); this.keyComparer = keyComparer; this.valueComparer = valueComparer; } public ImmutableDictionary ToImmutable() { return new ImmutableDictionary(root, keyComparer, valueComparer); } #region IDictionary implementation public void Add(TKey key, TValue value) { Add(new KeyValuePair(key, value)); } public bool ContainsKey(TKey key) { return !root.SearchNode(new KeyValuePair(key, default (TValue)), CompareKV).IsEmpty; } public bool Remove(TKey key) { bool found; root = root.RemoveFromNew(new KeyValuePair(key, default (TValue)), CompareKV, out found); return found; } public void SetItem(TKey key, TValue value) { if (ContainsKey(key)) Remove(key); Add(key, value); } public bool TryGetValue(TKey key, out TValue value) { var node = root.SearchNode(new KeyValuePair(key, default(TValue)), CompareKV); if (node.IsEmpty) { value = default (TValue); return false; } value = node.Value.Value; return true; } public TValue this [TKey key] { get { TValue value; if (TryGetValue(key, out value)) return value; throw new KeyNotFoundException(String.Format("Key: {0}", key)); } set { if (ContainsKey(key)) Remove(key); Add(key, value); } } ICollection IDictionary.Keys { get { return Keys.ToList(); } } public IEnumerable Keys { get { foreach (var kv in this) { yield return kv.Key; } } } ICollection IDictionary.Values { get { return Values.ToList(); } } public IEnumerable Values { get { foreach (var kv in this) { yield return kv.Value; } } } #endregion #region ICollection implementation public void Add(KeyValuePair item) { root = root.InsertIntoNew(item, CompareKV); } public void Clear() { root = new AvlNode>().ToMutable(); } public bool Contains(KeyValuePair item) { TValue value; if (!TryGetValue(item.Key, out value)) return false; return valueComparer.Equals(value, item.Value); } public void CopyTo(KeyValuePair[] array, int arrayIndex) { if (arrayIndex < 0 || arrayIndex + Count > array.Length) throw new ArgumentOutOfRangeException("arrayIndex"); foreach (var pair in this) { array[arrayIndex++] = pair; } } public bool Remove(KeyValuePair item) { if (!Contains(item)) return false; Remove(item.Key); return true; } public int Count { get { return root.Count; } } public bool IsReadOnly { get { return false; } } #endregion #region IEnumerable implementation public IEnumerator> GetEnumerator() { return root.GetEnumerator(false); } #endregion #region IEnumerable implementation IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } #endregion } } public static class ImmutableDictionary { public static bool Contains(this IImmutableDictionary dictionary, TKey key, TValue value) { if (dictionary == null) throw new ArgumentNullException("dictionary"); return dictionary.Contains(new KeyValuePair(key, value)); } public static ImmutableDictionary Create() where TKey : System.IComparable { return ImmutableDictionary.Empty; } public static ImmutableDictionary Create(IEqualityComparer keyComparer, IEqualityComparer valueComparer, IEnumerable> items) where TKey : System.IComparable { return Create(keyComparer, valueComparer).AddRange(items); } public static ImmutableDictionary Create(IEqualityComparer keyComparer, IEnumerable> items) where TKey : System.IComparable { return Create(keyComparer).AddRange(items); } public static ImmutableDictionary Create(IEnumerable> items) where TKey : System.IComparable { return Create().AddRange(items); } public static ImmutableDictionary Create(IEqualityComparer keyComparer, IEqualityComparer valueComparer) where TKey : System.IComparable { return Create().WithComparers(keyComparer, valueComparer); } public static ImmutableDictionary Create(IEqualityComparer keyComparer) where TKey : System.IComparable { return Create().WithComparers(keyComparer); } public static TValue GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key) where TKey : System.IComparable { return dictionary.GetValueOrDefault(key, default (TValue)); } public static TValue GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key, TValue defaultValue) where TKey : System.IComparable { if (dictionary == null) throw new ArgumentNullException("dictionary"); TValue result; if (dictionary.TryGetValue(key, out result)) return result; return defaultValue; } public static TValue GetValueOrDefault(this IDictionary dictionary, TKey key) where TKey : System.IComparable { return dictionary.GetValueOrDefault(key, default (TValue)); } public static TValue GetValueOrDefault(this IDictionary dictionary, TKey key, TValue defaultValue) where TKey : System.IComparable { if (dictionary == null) throw new ArgumentNullException("dictionary"); TValue result; if (dictionary.TryGetValue(key, out result)) return result; return defaultValue; } public static ImmutableDictionary ToImmutableDictionary(this IEnumerable> source, IEqualityComparer keyComparer) where TKey : System.IComparable { return source.ToImmutableDictionary(keyComparer, null); } public static ImmutableDictionary ToImmutableDictionary(this IEnumerable> source) where TKey : System.IComparable { return source.ToImmutableDictionary(null, null); } public static ImmutableDictionary ToImmutableDictionary(this IEnumerable> source, IEqualityComparer keyComparer, IEqualityComparer valueComparer) where TKey : System.IComparable { if (source == null) throw new ArgumentNullException("dictionary"); return Create(keyComparer, valueComparer).AddRange(source); } public static ImmutableDictionary ToImmutableDictionary(this IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer keyComparer) where TKey : System.IComparable { return source.ToImmutableDictionary(keySelector, elementSelector, keyComparer, null); } public static ImmutableDictionary ToImmutableDictionary(this IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer keyComparer, IEqualityComparer valueComparer) where TKey : System.IComparable { if (source == null) throw new ArgumentNullException("dictionary"); return Create(keyComparer, valueComparer).AddRange(source.Select(x => new KeyValuePair(keySelector(x), elementSelector(x)))); } public static ImmutableDictionary ToImmutableDictionary(this IEnumerable source, Func keySelector, Func elementSelector) where TKey : System.IComparable { return source.ToImmutableDictionary(keySelector, elementSelector, null, null); } } }