//****************************** // Written by Peter Golde // Copyright (c) 2004-2005, Wintellect // // Use and restribution of this code is subject to the license agreement // contained in the file "License.txt" accompanying this file. //****************************** using System; using System.Collections; using System.Collections.Generic; namespace Wintellect.PowerCollections { /// /// The MultiDictionary class that associates values with a key. Unlike an Dictionary, /// each key can have multiple values associated with it. When indexing an MultiDictionary, instead /// of a single value associated with a key, you retrieve an enumeration of values. /// When constructed, you can chose to allow the same value to be associated with a key multiple /// times, or only one time. /// /// The type of the keys. /// The of values associated with the keys. /// /// [Serializable] public class MultiDictionary : MultiDictionaryBase, ICloneable { // The comparer for comparing keys private IEqualityComparer keyEqualityComparer; // The comparer for comparing values; private IEqualityComparer valueEqualityComparer; // The comparer for compaing keys and values. private IEqualityComparer equalityComparer; // The hash that holds the keys and values. private Hash hash; // Whether duplicate values for the same key are allowed. private bool allowDuplicateValues; /// /// A structure to hold the key and the values associated with the key. /// The number of values must always be 1 or greater in a version that is stored, but /// can be zero in a dummy version used only for lookups. /// [Serializable] private struct KeyAndValues { /// /// The key. /// public TKey Key; /// /// The number of values. Always at least 1 except in a dummy version for lookups. /// public int Count; /// /// An array of values. /// public TValue[] Values; /// /// Create a dummy KeyAndValues with just the key, for lookups. /// /// The key to use. public KeyAndValues(TKey key) { this.Key = key; this.Count = 0; this.Values = null; } /// /// Make a copy of a KeyAndValues, copying the array. /// /// KeyAndValues to copy. /// A copied version. public static KeyAndValues Copy(KeyAndValues x) { KeyAndValues result; result.Key = x.Key; result.Count = x.Count; if (x.Values != null) result.Values = (TValue[])x.Values.Clone(); else result.Values = null; return result; } } /// /// This class implements IEqualityComparer for KeysAndValues, allowing them to be /// compared by their keys. An IEqualityComparer on keys is required. /// [Serializable] private class KeyAndValuesEqualityComparer : IEqualityComparer { private IEqualityComparer keyEqualityComparer; public KeyAndValuesEqualityComparer(IEqualityComparer keyEqualityComparer) { this.keyEqualityComparer = keyEqualityComparer; } public bool Equals(KeyAndValues x, KeyAndValues y) { return keyEqualityComparer.Equals(x.Key, y.Key); } public int GetHashCode(KeyAndValues obj) { return Util.GetHashCode(obj.Key, keyEqualityComparer); } } #region Constructors /// /// Create a new MultiDictionary. The default ordering of keys and values are used. If duplicate values /// are allowed, multiple copies of the same value can be associated with the same key. For example, the key "foo" /// could have "a", "a", and "b" associated with it. If duplicate values are not allowed, only one copies of a given value can /// be associated with the same key, although different keys can have the same value. For example, the key "foo" could /// have "a" and "b" associated with it, which key "bar" has values "b" and "c" associated with it. /// /// The default ordering of keys and values will be used, as defined by TKey and TValue's implementation /// of IComparable<T> (or IComparable if IComparable<T> is not implemented). If a different ordering should be /// used, other constructors allow a custom Comparer or IComparer to be passed to changed the ordering. /// Can the same value be associated with a key multiple times? /// TKey or TValue does not implement either IComparable<T> or IComparable. public MultiDictionary(bool allowDuplicateValues) : this(allowDuplicateValues, EqualityComparer.Default, EqualityComparer.Default) { } /// /// Create a new MultiDictionary. If duplicate values /// are allowed, multiple copies of the same value can be associated with the same key. For example, the key "foo" /// could have "a", "a", and "b" associated with it. If duplicate values are not allowed, only one copies of a given value can /// be associated with the same key, although different keys can have the same value. For example, the key "foo" could /// have "a" and "b" associated with it, which key "bar" has values "b" and "c" associated with it. /// /// Can the same value be associated with a key multiple times? /// An IEqualityComparer<TKey> instance that will be used to compare keys. /// TValue does not implement either IComparable<TValue> or IComparable. public MultiDictionary(bool allowDuplicateValues, IEqualityComparer keyEqualityComparer) : this(allowDuplicateValues, keyEqualityComparer, EqualityComparer.Default) { } /// /// Create a new MultiDictionary. If duplicate values /// are allowed, multiple copies of the same value can be associated with the same key. For example, the key "foo" /// could have "a", "a", and "b" associated with it. If duplicate values are not allowed, only one copies of a given value can /// be associated with the same key, although different keys can have the same value. For example, the key "foo" could /// have "a" and "b" associated with it, which key "bar" has values "b" and "c" associated with it. /// /// Can the same value be associated with a key multiple times? /// An IEqualityComparer<TKey> instance that will be used to compare keys. /// An IEqualityComparer<TValue> instance that will be used to compare values. public MultiDictionary(bool allowDuplicateValues, IEqualityComparer keyEqualityComparer, IEqualityComparer valueEqualityComparer) { if (keyEqualityComparer == null) throw new ArgumentNullException("keyEqualityComparer"); if (valueEqualityComparer == null) throw new ArgumentNullException("valueEqualityComparer"); this.allowDuplicateValues = allowDuplicateValues; this.keyEqualityComparer = keyEqualityComparer; this.valueEqualityComparer = valueEqualityComparer; this.equalityComparer = new KeyAndValuesEqualityComparer(keyEqualityComparer); this.hash = new Hash(equalityComparer); } /// /// Create a new MultiDictionary. Private constructor, for use by Clone(). /// private MultiDictionary(bool allowDuplicateValues, IEqualityComparer keyEqualityComparer, IEqualityComparer valueEqualityComparer, IEqualityComparer equalityComparer, Hash hash) { if (keyEqualityComparer == null) throw new ArgumentNullException("keyEqualityComparer"); if (valueEqualityComparer == null) throw new ArgumentNullException("valueEqualityComparer"); this.allowDuplicateValues = allowDuplicateValues; this.keyEqualityComparer = keyEqualityComparer; this.valueEqualityComparer = valueEqualityComparer; this.equalityComparer = equalityComparer; this.hash = hash; } #endregion Constructors #region Add or remove items /// /// Adds a new value to be associated with a key. If duplicate values are permitted, this /// method always adds a new key-value pair to the dictionary. /// If duplicate values are not permitted, and already has a value /// equal to associated with it, then that value is replaced with , /// and the number of values associate with is unchanged. /// /// The key to associate with. /// The value to associated with . public sealed override void Add(TKey key, TValue value) { KeyAndValues keyValues = new KeyAndValues(key); KeyAndValues existing; if (hash.Find(keyValues, false, out existing)) { // There already is an item in the hash table equal to this key. Add the new value, // taking into account duplicates if needed. int existingCount = existing.Count; if (!allowDuplicateValues) { int valueHash = Util.GetHashCode(value, valueEqualityComparer); for (int i = 0; i < existingCount; ++i) { if (Util.GetHashCode(existing.Values[i], valueEqualityComparer) == valueHash && valueEqualityComparer.Equals(existing.Values[i], value)) { // Found an equal existing value. Replace it and we're done. existing.Values[i] = value; return; } } } // Add a new value to an existing key. if (existingCount == existing.Values.Length) { // Grow the array to make room. TValue[] newValues = new TValue[existingCount * 2]; Array.Copy(existing.Values, newValues, existingCount); existing.Values = newValues; } existing.Values[existingCount] = value; existing.Count = existingCount + 1; // Update the hash table. hash.Find(existing, true, out keyValues); return; } else { // No item with this key. Add it. keyValues.Count = 1; keyValues.Values = new TValue[1] { value }; hash.Insert(keyValues, true, out existing); return; } } /// /// Removes a given value from the values associated with a key. If the /// last value is removed from a key, the key is removed also. /// /// A key to remove a value from. /// The value to remove. /// True if was associated with (and was /// therefore removed). False if was not associated with . public sealed override bool Remove(TKey key, TValue value) { KeyAndValues keyValues = new KeyAndValues(key); KeyAndValues existing; if (hash.Find(keyValues, false, out existing)) { // There is an item in the hash table equal to this key. Find the value. int existingCount = existing.Count; int valueHash = Util.GetHashCode(value, valueEqualityComparer); int indexFound = -1; for (int i = 0; i < existingCount; ++i) { if (Util.GetHashCode(existing.Values[i], valueEqualityComparer) == valueHash && valueEqualityComparer.Equals(existing.Values[i], value)) { // Found an equal existing value indexFound = i; } } if (existingCount == 1) { // Removing the last value. Remove the key. hash.Delete(existing, out keyValues); return true; } else if (indexFound >= 0) { // Found a value. Remove it. if (indexFound < existingCount - 1) Array.Copy(existing.Values, indexFound + 1, existing.Values, indexFound, existingCount - indexFound - 1); existing.Count = existingCount - 1; // Update the hash. hash.Find(existing, true, out keyValues); return true; } else { // Value was not found. return false; } } else { return false; // key not found. } } /// /// Removes a key and all associated values from the dictionary. If the /// key is not present in the dictionary, it is unchanged and false is returned. /// /// The key to remove. /// True if the key was present and was removed. Returns /// false if the key was not present. public sealed override bool Remove(TKey key) { KeyAndValues dummy; return hash.Delete(new KeyAndValues(key), out dummy); } /// /// Removes all keys and values from the dictionary. /// public sealed override void Clear() { hash.StopEnumerations(); // Invalidate any enumerations. // The simplest and fastest way is simply to throw away the old hash and create a new one. hash = new Hash(equalityComparer); } #endregion Add or remove items #region Query items /// /// Returns the IEqualityComparer<T> used to compare keys in this dictionary. /// /// If the dictionary was created using a comparer, that comparer is returned. Otherwise /// the default comparer for TKey (EqualityComparer<TKey>.Default) is returned. public IEqualityComparer KeyComparer { get { return this.keyEqualityComparer; } } /// /// Returns the IEqualityComparer<T> used to compare values in this dictionary. /// /// If the dictionary was created using a comparer, that comparer is returned. Otherwise /// the default comparer for TValue (EqualityComparer<TValue>.Default) is returned. public IEqualityComparer ValueComparer { get { return this.valueEqualityComparer; } } /// /// Determine if two values are equal. /// /// First value to compare. /// Second value to compare. /// True if the values are equal. protected sealed override bool EqualValues(TValue value1, TValue value2) { return valueEqualityComparer.Equals(value1, value2); } /// /// Gets the number of key-value pairs in the dictionary. Each value associated /// with a given key is counted. If duplicate values are permitted, each duplicate /// value is included in the count. /// /// The number of key-value pairs in the dictionary. public sealed override int Count { get { return hash.ElementCount; } } /// /// Checks to see if is associated with /// in the dictionary. /// /// The key to check. /// The value to check. /// True if is associated with . public sealed override bool Contains(TKey key, TValue value) { KeyAndValues find = new KeyAndValues(key); KeyAndValues item; if (hash.Find(find, false, out item)) { int existingCount = item.Count; int valueHash = Util.GetHashCode(value, valueEqualityComparer); for (int i = 0; i < existingCount; ++i) { if (Util.GetHashCode(item.Values[i], valueEqualityComparer) == valueHash && valueEqualityComparer.Equals(item.Values[i], value)) { // Found an equal existing value. return true; } } } return false; } /// /// Checks to see if the key is present in the dictionary and has /// at least one value associated with it. /// /// The key to check. /// True if is present and has at least /// one value associated with it. Returns false otherwise. public sealed override bool ContainsKey(TKey key) { KeyAndValues find = new KeyAndValues(key); KeyAndValues temp; return hash.Find(find, false, out temp); } /// /// Enumerate all the keys in the dictionary. /// /// An IEnumerator<TKey> that enumerates all of the keys in the dictionary that /// have at least one value associated with them. protected sealed override IEnumerator EnumerateKeys() { foreach (KeyAndValues item in hash) { yield return item.Key; } } /// /// Enumerate the values in the a KeyAndValues structure. Can't return /// the array directly because: /// a) The array might be larger than the count. /// b) We can't allow clients to down-cast to the array and modify it. /// c) We have to abort enumeration if the hash changes. /// /// Item with the values to enumerate.. /// An enumerable that enumerates the items in the KeyAndValues structure. private IEnumerator EnumerateValues(KeyAndValues keyAndValues) { int count = keyAndValues.Count; int stamp = hash.GetEnumerationStamp(); for (int i = 0; i < count; ++i) { yield return keyAndValues.Values[i]; hash.CheckEnumerationStamp(stamp); } } /// /// Determines if this dictionary contains a key equal to . If so, all the values /// associated with that key are returned through the values parameter. /// /// The key to search for. /// Returns all values associated with key, if true was returned. /// True if the dictionary contains key. False if the dictionary does not contain key. protected sealed override bool TryEnumerateValuesForKey(TKey key, out IEnumerator values) { KeyAndValues find = new KeyAndValues(key); KeyAndValues item; if (hash.Find(find, false, out item)) { values = EnumerateValues(item); return true; } else { values = null; return false; } } /// /// Gets the number of values associated with a given key. /// /// The key to count values of. /// The number of values associated with . If /// is not present in the dictionary, zero is returned. protected sealed override int CountValues(TKey key) { KeyAndValues find = new KeyAndValues(key); KeyAndValues item; if (hash.Find(find, false, out item)) { return item.Count; } else { return 0; } } #endregion Query items #region Cloning /// /// Makes a shallow clone of this dictionary; i.e., if keys or values of the /// dictionary are reference types, then they are not cloned. If TKey or TValue is a value type, /// then each element is copied as if by simple assignment. /// /// Cloning the dictionary takes time O(N), where N is the number of key-value pairs in the dictionary. /// The cloned dictionary. public MultiDictionary Clone() { return new MultiDictionary(allowDuplicateValues, keyEqualityComparer, valueEqualityComparer, equalityComparer, hash.Clone(KeyAndValues.Copy)); } /// /// Implements ICloneable.Clone. Makes a shallow clone of this dictionary; i.e., if keys or values are reference types, then they are not cloned. /// /// The cloned dictionary. object ICloneable.Clone() { return Clone(); } /// /// Throw an InvalidOperationException indicating that this type is not cloneable. /// /// Type to test. private void NonCloneableType(Type t) { throw new InvalidOperationException(string.Format(Strings.TypeNotCloneable, t.FullName)); } /// /// Makes a deep clone of this dictionary. A new dictionary is created with a clone of /// each entry of this dictionary, by calling ICloneable.Clone on each element. If TKey or TValue is /// a value type, then each element is copied as if by simple assignment. /// /// If TKey or TValue is a reference type, it must implement /// ICloneable. Otherwise, an InvalidOperationException is thrown. /// Cloning the dictionary takes time O(N log N), where N is the number of key-value pairs in the dictionary. /// The cloned dictionary. /// TKey or TValue is a reference type that does not implement ICloneable. public MultiDictionary CloneContents() { bool keyIsValueType, valueIsValueType; // Make sure that TKey and TValue can be cloned. if (!Util.IsCloneableType(typeof(TKey), out keyIsValueType)) NonCloneableType(typeof(TKey)); if (!Util.IsCloneableType(typeof(TValue), out valueIsValueType)) NonCloneableType(typeof(TValue)); // It's tempting to do a more efficient cloning, utilizing the hash.Clone() method. However, we can't know that // the cloned version of the key has the same hash value. MultiDictionary newDict = new MultiDictionary(allowDuplicateValues, keyEqualityComparer, valueEqualityComparer); foreach (KeyAndValues item in hash) { // Clone the key and values parts. Value types can be cloned // by just copying them, otherwise, ICloneable is used. TKey keyClone; TValue[] valuesClone; if (keyIsValueType) keyClone = item.Key; else { if (item.Key == null) keyClone = default(TKey); // Really null, because we know TKey isn't a value type. else keyClone = (TKey)(((ICloneable)item.Key).Clone()); } valuesClone = new TValue[item.Count]; if (valueIsValueType) Array.Copy(item.Values, valuesClone, item.Count); else { for (int i = 0; i < item.Count; ++i) { if (item.Values[i] == null) valuesClone[i] = default(TValue); // Really null, because we know TKey isn't a value type. else valuesClone[i] = (TValue)(((ICloneable)item.Values[i]).Clone()); } } newDict.AddMany(keyClone, valuesClone); } return newDict; } #endregion Cloning } }