using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; namespace Cryville.Common.Collections.Specialized { /// /// Represents a collection of keys and values. Identical to but much faster. /// /// The type of the values in the dictionary. [DebuggerTypeProxy(typeof(IntKeyedDictionaryDebugView<>))] [DebuggerDisplay("Count = {Count}")] public class IntKeyedDictionary : IDictionary, IDictionary, IReadOnlyDictionary { private struct Entry { public int next; // Index of next entry, -1 if last public int key; // Key of entry public T value; // Value of entry } private int[] buckets; private Entry[] entries; private int count; private int version; private int freeList; private int freeCount; private KeyCollection keys; private ValueCollection values; private Object _syncRoot; public IntKeyedDictionary() : this(0) { } public IntKeyedDictionary(int capacity) { if (capacity < 0) throw new ArgumentOutOfRangeException("capacity"); if (capacity > 0) Initialize(capacity); } public IntKeyedDictionary(IDictionary dictionary) : this(dictionary != null ? dictionary.Count : 0) { if (dictionary == null) { throw new ArgumentNullException("dictionary"); } foreach (KeyValuePair pair in dictionary) { Add(pair.Key, pair.Value); } } public int Count { get { return count - freeCount; } } public KeyCollection Keys { get { Contract.Ensures(Contract.Result() != null); if (keys == null) keys = new KeyCollection(this); return keys; } } ICollection IDictionary.Keys { get { if (keys == null) keys = new KeyCollection(this); return keys; } } IEnumerable IReadOnlyDictionary.Keys { get { if (keys == null) keys = new KeyCollection(this); return keys; } } public ValueCollection Values { get { Contract.Ensures(Contract.Result() != null); if (values == null) values = new ValueCollection(this); return values; } } ICollection IDictionary.Values { get { if (values == null) values = new ValueCollection(this); return values; } } IEnumerable IReadOnlyDictionary.Values { get { if (values == null) values = new ValueCollection(this); return values; } } public T this[int key] { get { int i = FindEntry(key); if (i >= 0) return entries[i].value; throw new KeyNotFoundException(); } set { Insert(key, value, false); } } public void Add(int key, T value) { Insert(key, value, true); } void ICollection>.Add(KeyValuePair keyValuePair) { Add(keyValuePair.Key, keyValuePair.Value); } bool ICollection>.Contains(KeyValuePair keyValuePair) { int i = FindEntry(keyValuePair.Key); if (i >= 0) { return true; } return false; } bool ICollection>.Remove(KeyValuePair keyValuePair) { int i = FindEntry(keyValuePair.Key); if (i >= 0) { Remove(keyValuePair.Key); return true; } return false; } public void Clear() { if (count > 0) { for (int i = 0; i < buckets.Length; i++) buckets[i] = -1; Array.Clear(entries, 0, count); freeList = -1; count = 0; freeCount = 0; version++; } } public bool ContainsKey(int key) { return FindEntry(key) >= 0; } public bool ContainsValue(T value) { if (value == null) { for (int i = 0; i < count; i++) { if (entries[i].key >= 0 && entries[i].value == null) return true; } } else { EqualityComparer c = EqualityComparer.Default; for (int i = 0; i < count; i++) { if (entries[i].key >= 0 && c.Equals(entries[i].value, value)) return true; } } return false; } private void CopyTo(KeyValuePair[] array, int index) { if (array == null) { throw new ArgumentNullException("array"); } if (index < 0 || index > array.Length) { throw new ArgumentOutOfRangeException("index", "Non-negative number required."); } if (array.Length - index < Count) { throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length."); } int count = this.count; Entry[] entries = this.entries; for (int i = 0; i < count; i++) { if (entries[i].key >= 0) { array[index++] = new KeyValuePair(entries[i].key, entries[i].value); } } } public Enumerator GetEnumerator() { return new Enumerator(this, Enumerator.KeyValuePair); } IEnumerator> IEnumerable>.GetEnumerator() { return new Enumerator(this, Enumerator.KeyValuePair); } private int FindEntry(int key) { if (buckets != null) { for (int i = buckets[key % buckets.Length]; i >= 0; i = entries[i].next) { if (entries[i].key == key) return i; } } return -1; } private void Initialize(int capacity) { int size = HashHelpers.GetPrime(capacity); buckets = new int[size]; for (int i = 0; i < buckets.Length; i++) buckets[i] = -1; entries = new Entry[size]; freeList = -1; } private void Insert(int key, T value, bool add) { if (buckets == null) Initialize(0); int targetBucket = key % buckets.Length; #if FEATURE_RANDOMIZED_STRING_HASHING int collisionCount = 0; #endif for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) { if (entries[i].key == key) { if (add) { throw new ArgumentException("An item with the same key has already been added."); } entries[i].value = value; version++; return; } #if FEATURE_RANDOMIZED_STRING_HASHING collisionCount++; #endif } int index; if (freeCount > 0) { index = freeList; freeList = entries[index].next; freeCount--; } else { if (count == entries.Length) { Resize(); targetBucket = key % buckets.Length; } index = count; count++; } entries[index].next = buckets[targetBucket]; entries[index].key = key; entries[index].value = value; buckets[targetBucket] = index; version++; } private void Resize() { Resize(HashHelpers.ExpandPrime(count), false); } private void Resize(int newSize, bool forceNewHashCodes) { Contract.Assert(newSize >= entries.Length); int[] newBuckets = new int[newSize]; for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1; Entry[] newEntries = new Entry[newSize]; Array.Copy(entries, 0, newEntries, 0, count); if (forceNewHashCodes) { for (int i = 0; i < count; i++) { if (newEntries[i].key != -1) { newEntries[i].key = newEntries[i].key; } } } for (int i = 0; i < count; i++) { if (newEntries[i].key >= 0) { int bucket = newEntries[i].key % newSize; newEntries[i].next = newBuckets[bucket]; newBuckets[bucket] = i; } } buckets = newBuckets; entries = newEntries; } public bool Remove(int key) { if (buckets != null) { int bucket = key % buckets.Length; int last = -1; for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next) { if (entries[i].key == key) { if (last < 0) { buckets[bucket] = entries[i].next; } else { entries[last].next = entries[i].next; } entries[i].key = -1; entries[i].next = freeList; entries[i].value = default(T); freeList = i; freeCount++; version++; return true; } } } return false; } public bool TryGetValue(int key, out T value) { int i = FindEntry(key); if (i >= 0) { value = entries[i].value; return true; } value = default(T); return false; } // This is a convenience method for the internal callers that were converted from using Hashtable. // Many were combining key doesn't exist and key exists but null value (for non-value types) checks. // This allows them to continue getting that behavior with minimal code delta. This is basically // TryGetValue without the out param internal T GetValueOrDefault(int key) { int i = FindEntry(key); if (i >= 0) { return entries[i].value; } return default(T); } bool ICollection>.IsReadOnly { get { return false; } } void ICollection>.CopyTo(KeyValuePair[] array, int index) { CopyTo(array, index); } void ICollection.CopyTo(Array array, int index) { if (array == null) { throw new ArgumentNullException("array"); } if (array.Rank != 1) { throw new ArgumentException("Only single dimensional arrays are supported for the requested action."); } if (array.GetLowerBound(0) != 0) { throw new ArgumentException("The lower bound of target array must be zero."); } if (index < 0 || index > array.Length) { throw new ArgumentOutOfRangeException("index", "Non-negative number required."); } if (array.Length - index < Count) { throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length."); } KeyValuePair[] pairs = array as KeyValuePair[]; if (pairs != null) { CopyTo(pairs, index); } else if (array is DictionaryEntry[]) { DictionaryEntry[] dictEntryArray = array as DictionaryEntry[]; Entry[] entries = this.entries; for (int i = 0; i < count; i++) { if (entries[i].key >= 0) { dictEntryArray[index++] = new DictionaryEntry(entries[i].key, entries[i].value); } } } else { object[] objects = array as object[]; if (objects == null) { throw new ArgumentException("Target array type is not compatible with the type of items in the collection."); } try { int count = this.count; Entry[] entries = this.entries; for (int i = 0; i < count; i++) { if (entries[i].key >= 0) { objects[index++] = new KeyValuePair(entries[i].key, entries[i].value); } } } catch (ArrayTypeMismatchException) { throw new ArgumentException("Target array type is not compatible with the type of items in the collection."); } } } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(this, Enumerator.KeyValuePair); } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { if (_syncRoot == null) { System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null); } return _syncRoot; } } bool IDictionary.IsFixedSize { get { return false; } } bool IDictionary.IsReadOnly { get { return false; } } ICollection IDictionary.Keys { get { return (ICollection)Keys; } } ICollection IDictionary.Values { get { return (ICollection)Values; } } object IDictionary.this[object key] { get { if (IsCompatibleKey(key)) { int i = FindEntry((int)key); if (i >= 0) { return entries[i].value; } } return null; } set { if (key == null) { throw new ArgumentNullException("key"); } if (value == null && default(T) != null) { throw new ArgumentNullException("value"); } try { int tempKey = (int)key; try { this[tempKey] = (T)value; } catch (InvalidCastException) { throw new ArgumentException(string.Format("The value \"{0}\" is not of type \"{1}\" and cannot be used in this generic collection.", value, typeof(T)), "value"); } } catch (InvalidCastException) { throw new ArgumentException(string.Format("The value \"{0}\" is not of type \"{1}\" and cannot be used in this generic collection.", key, typeof(int)), "key"); } } } private static bool IsCompatibleKey(object key) { if (key == null) { throw new ArgumentNullException("key"); } return (key is int); } void IDictionary.Add(object key, object value) { if (key == null) { throw new ArgumentNullException("key"); } if (value == null && default(T) != null) { throw new ArgumentNullException("value"); } try { int tempKey = (int)key; try { Add(tempKey, (T)value); } catch (InvalidCastException) { throw new ArgumentException(string.Format("The value \"{0}\" is not of type \"{1}\" and cannot be used in this generic collection.", value, typeof(T)), "value"); } } catch (InvalidCastException) { throw new ArgumentException(string.Format("The value \"{0}\" is not of type \"{1}\" and cannot be used in this generic collection.", key, typeof(int)), "key"); } } bool IDictionary.Contains(object key) { if (IsCompatibleKey(key)) { return ContainsKey((int)key); } return false; } IDictionaryEnumerator IDictionary.GetEnumerator() { return new Enumerator(this, Enumerator.DictEntry); } void IDictionary.Remove(object key) { if (IsCompatibleKey(key)) { Remove((int)key); } } [Serializable] public struct Enumerator : IEnumerator>, IDictionaryEnumerator { private readonly IntKeyedDictionary dictionary; private readonly int version; private int index; private KeyValuePair current; private readonly int getEnumeratorRetType; // What should Enumerator.Current return? internal const int DictEntry = 1; internal const int KeyValuePair = 2; internal Enumerator(IntKeyedDictionary dictionary, int getEnumeratorRetType) { this.dictionary = dictionary; version = dictionary.version; index = 0; this.getEnumeratorRetType = getEnumeratorRetType; current = new KeyValuePair(); } public bool MoveNext() { if (version != dictionary.version) { throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); } // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends. // dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue while ((uint)index < (uint)dictionary.count) { if (dictionary.entries[index].key >= 0) { current = new KeyValuePair(dictionary.entries[index].key, dictionary.entries[index].value); index++; return true; } index++; } index = dictionary.count + 1; current = new KeyValuePair(); return false; } public KeyValuePair Current { get { return current; } } public void Dispose() { } object IEnumerator.Current { get { if (index == 0 || (index == dictionary.count + 1)) { throw new InvalidOperationException("Enumeration has either not started or has already finished."); } if (getEnumeratorRetType == DictEntry) { return new System.Collections.DictionaryEntry(current.Key, current.Value); } else { return new KeyValuePair(current.Key, current.Value); } } } void IEnumerator.Reset() { if (version != dictionary.version) { throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); } index = 0; current = new KeyValuePair(); } DictionaryEntry IDictionaryEnumerator.Entry { get { if (index == 0 || (index == dictionary.count + 1)) { throw new InvalidOperationException("Enumeration has either not started or has already finished."); } return new DictionaryEntry(current.Key, current.Value); } } object IDictionaryEnumerator.Key { get { if (index == 0 || (index == dictionary.count + 1)) { throw new InvalidOperationException("Enumeration has either not started or has already finished."); } return current.Key; } } object IDictionaryEnumerator.Value { get { if (index == 0 || (index == dictionary.count + 1)) { throw new InvalidOperationException("Enumeration has either not started or has already finished."); } return current.Value; } } } [DebuggerTypeProxy(typeof(IntKeyedDictionaryKeyCollectionDebugView<>))] [DebuggerDisplay("Count = {Count}")] [Serializable] public sealed class KeyCollection : ICollection, ICollection, IReadOnlyCollection { private readonly IntKeyedDictionary dictionary; public KeyCollection(IntKeyedDictionary dictionary) { if (dictionary == null) { throw new ArgumentNullException("dictionary"); } this.dictionary = dictionary; } public Enumerator GetEnumerator() { return new Enumerator(dictionary); } public void CopyTo(int[] array, int index) { if (array == null) { throw new ArgumentNullException("array"); } if (index < 0 || index > array.Length) { throw new ArgumentOutOfRangeException("index", "Non-negative number required."); } if (array.Length - index < dictionary.Count) { throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length."); } int count = dictionary.count; Entry[] entries = dictionary.entries; for (int i = 0; i < count; i++) { if (entries[i].key >= 0) array[index++] = entries[i].key; } } public int Count { get { return dictionary.Count; } } bool ICollection.IsReadOnly { get { return true; } } void ICollection.Add(int item) { throw new NotSupportedException("Mutating a key collection derived from a dictionary is not allowed."); } void ICollection.Clear() { throw new NotSupportedException("Mutating a key collection derived from a dictionary is not allowed."); } bool ICollection.Contains(int item) { return dictionary.ContainsKey(item); } bool ICollection.Remove(int item) { throw new NotSupportedException("Mutating a key collection derived from a dictionary is not allowed."); } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(dictionary); } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(dictionary); } void ICollection.CopyTo(Array array, int index) { if (array == null) { throw new ArgumentNullException("array"); } if (array.Rank != 1) { throw new ArgumentException("Only single dimensional arrays are supported for the requested action."); } if (array.GetLowerBound(0) != 0) { throw new ArgumentException("The lower bound of target array must be zero."); } if (index < 0 || index > array.Length) { throw new ArgumentOutOfRangeException("index", "Non-negative number required."); } if (array.Length - index < dictionary.Count) { throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length."); } int[] keys = array as int[]; if (keys != null) { CopyTo(keys, index); } else { object[] objects = array as object[]; if (objects == null) { throw new ArgumentException("Target array type is not compatible with the type of items in the collection."); } int count = dictionary.count; Entry[] entries = dictionary.entries; try { for (int i = 0; i < count; i++) { if (entries[i].key >= 0) objects[index++] = entries[i].key; } } catch (ArrayTypeMismatchException) { throw new ArgumentException("Target array type is not compatible with the type of items in the collection."); } } } bool ICollection.IsSynchronized { get { return false; } } Object ICollection.SyncRoot { get { return ((ICollection)dictionary).SyncRoot; } } [Serializable] public struct Enumerator : IEnumerator, System.Collections.IEnumerator { private readonly IntKeyedDictionary dictionary; private int index; private readonly int version; private int currentKey; internal Enumerator(IntKeyedDictionary dictionary) { this.dictionary = dictionary; version = dictionary.version; index = 0; currentKey = default(int); } public void Dispose() { } public bool MoveNext() { if (version != dictionary.version) { throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); } while ((uint)index < (uint)dictionary.count) { if (dictionary.entries[index].key >= 0) { currentKey = dictionary.entries[index].key; index++; return true; } index++; } index = dictionary.count + 1; currentKey = default(int); return false; } public int Current { get { return currentKey; } } Object System.Collections.IEnumerator.Current { get { if (index == 0 || (index == dictionary.count + 1)) { throw new InvalidOperationException("Enumeration has either not started or has already finished."); } return currentKey; } } void System.Collections.IEnumerator.Reset() { if (version != dictionary.version) { throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); } index = 0; currentKey = default(int); } } } [DebuggerTypeProxy(typeof(IntKeyedDictionaryValueCollectionDebugView<>))] [DebuggerDisplay("Count = {Count}")] [Serializable] public sealed class ValueCollection : ICollection, ICollection, IReadOnlyCollection { private readonly IntKeyedDictionary dictionary; public ValueCollection(IntKeyedDictionary dictionary) { if (dictionary == null) { throw new ArgumentNullException("dictionary"); } this.dictionary = dictionary; } public Enumerator GetEnumerator() { return new Enumerator(dictionary); } public void CopyTo(T[] array, int index) { if (array == null) { throw new ArgumentNullException("array"); } if (index < 0 || index > array.Length) { throw new ArgumentOutOfRangeException("index", "Non-negative number required."); } if (array.Length - index < dictionary.Count) { throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length."); } int count = dictionary.count; Entry[] entries = dictionary.entries; for (int i = 0; i < count; i++) { if (entries[i].key >= 0) array[index++] = entries[i].value; } } public int Count { get { return dictionary.Count; } } bool ICollection.IsReadOnly { get { return true; } } void ICollection.Add(T item) { throw new NotSupportedException("Mutating a value collection derived from a dictionary is not allowed."); } bool ICollection.Remove(T item) { throw new NotSupportedException("Mutating a value collection derived from a dictionary is not allowed."); } void ICollection.Clear() { throw new NotSupportedException("Mutating a value collection derived from a dictionary is not allowed."); } bool ICollection.Contains(T item) { return dictionary.ContainsValue(item); } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(dictionary); } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(dictionary); } void ICollection.CopyTo(Array array, int index) { if (array == null) { throw new ArgumentNullException("array"); } if (array.Rank != 1) { throw new ArgumentException("Only single dimensional arrays are supported for the requested action."); } if (array.GetLowerBound(0) != 0) { throw new ArgumentException("The lower bound of target array must be zero."); } if (index < 0 || index > array.Length) { throw new ArgumentOutOfRangeException("index", "Non-negative number required."); } if (array.Length - index < dictionary.Count) { throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length."); } T[] values = array as T[]; if (values != null) { CopyTo(values, index); } else { object[] objects = array as object[]; if (objects == null) { throw new ArgumentException("Target array type is not compatible with the type of items in the collection."); } int count = dictionary.count; Entry[] entries = dictionary.entries; try { for (int i = 0; i < count; i++) { if (entries[i].key >= 0) objects[index++] = entries[i].value; } } catch (ArrayTypeMismatchException) { throw new ArgumentException("Target array type is not compatible with the type of items in the collection."); } } } bool ICollection.IsSynchronized { get { return false; } } Object ICollection.SyncRoot { get { return ((ICollection)dictionary).SyncRoot; } } [Serializable] public struct Enumerator : IEnumerator, IEnumerator { private readonly IntKeyedDictionary dictionary; private int index; private readonly int version; private T currentValue; internal Enumerator(IntKeyedDictionary dictionary) { this.dictionary = dictionary; version = dictionary.version; index = 0; currentValue = default(T); } public void Dispose() { } public bool MoveNext() { if (version != dictionary.version) { throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); } while ((uint)index < (uint)dictionary.count) { if (dictionary.entries[index].key >= 0) { currentValue = dictionary.entries[index].value; index++; return true; } index++; } index = dictionary.count + 1; currentValue = default(T); return false; } public T Current { get { return currentValue; } } Object System.Collections.IEnumerator.Current { get { if (index == 0 || (index == dictionary.count + 1)) { throw new InvalidOperationException("Enumeration has either not started or has already finished."); } return currentValue; } } void System.Collections.IEnumerator.Reset() { if (version != dictionary.version) { throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); } index = 0; currentValue = default(T); } } } } internal sealed class IntKeyedDictionaryDebugView { private readonly IntKeyedDictionary dict; public IntKeyedDictionaryDebugView(IntKeyedDictionary dictionary) { if (dictionary == null) throw new ArgumentNullException("dictionary"); this.dict = dictionary; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public KeyValuePair[] Items { get { KeyValuePair[] items = new KeyValuePair[dict.Count]; ((IDictionary)dict).CopyTo(items, 0); return items; } } } internal sealed class IntKeyedDictionaryKeyCollectionDebugView { private readonly IntKeyedDictionary collection; public IntKeyedDictionaryKeyCollectionDebugView(IntKeyedDictionary collection) { if (collection == null) throw new ArgumentNullException("collection"); this.collection = collection; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { T[] items = new T[collection.Count]; ((IDictionary)collection).CopyTo(items, 0); return items; } } } internal sealed class IntKeyedDictionaryValueCollectionDebugView { private readonly IntKeyedDictionary collection; public IntKeyedDictionaryValueCollectionDebugView(IntKeyedDictionary collection) { if (collection == null) throw new ArgumentNullException("collection"); this.collection = collection; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { T[] items = new T[collection.Count]; ((IDictionary)collection).CopyTo(items, 0); return items; } } } }