//****************************** // 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 { /// /// OrderedDictionary<TKey, TValue> is a collection that maps keys of type TKey /// to values of type TValue. The keys are maintained in a sorted order, and at most one value /// is permitted for each key. /// /// ///

The keys are compared in one of three ways. If TKey implements IComparable<TKey> or IComparable, /// then the CompareTo method of that interface will be used to compare elements. Alternatively, a comparison /// function can be passed in either as a delegate, or as an instance of IComparer<TKey>.

///

OrderedDictionary is implemented as a balanced binary tree. Inserting, deleting, and looking up an /// an element all are done in log(N) type, where N is the number of keys in the tree.

///

is similar, but uses hashing instead of comparison, and does not maintain /// the keys in sorted order.

///
/// [Serializable] public class OrderedDictionary: DictionaryBase, ICloneable { // The comparer for comparing keys. This is saved to return from the Comparer property, // but is otherwise not used. private IComparer keyComparer; // The comparer for comparing key-value pairs. private IComparer> pairComparer; private RedBlackTree> tree; /// /// Helper function to create a new KeyValuePair struct. /// /// The key. /// The value. /// A new KeyValuePair. private static KeyValuePair NewPair(TKey key, TValue value) { KeyValuePair pair = new KeyValuePair(key,value); return pair; } /// /// Helper function to create a new KeyValuePair struct with a default value. /// /// The key. /// A new KeyValuePair. private static KeyValuePair NewPair(TKey key) { KeyValuePair pair = new KeyValuePair(key, default(TValue)); return pair; } /// /// Creates a new OrderedDictionary. The TKey must implemented IComparable<TKey> /// or IComparable. /// The CompareTo method of this interface will be used to compare keys in this dictionary. /// /// TKey does not implement IComparable<TKey>. public OrderedDictionary() : this(Comparers.DefaultComparer()) { } /// /// Creates a new OrderedDictionary. The Compare method of the passed comparison object /// will be used to compare keys in this dictionary. /// /// /// The GetHashCode and Equals methods of the provided IComparer<TKey> will never /// be called, and need not be implemented. /// An instance of IComparer<TKey> that will be used to compare keys. public OrderedDictionary(IComparer comparer) : this(null, comparer, Comparers.ComparerKeyValueFromComparerKey(comparer)) { if (comparer == null) throw new ArgumentNullException("comparer"); } /// /// Creates a new OrderedDictionary. The passed delegate will be used to compare keys in this dictionary. /// /// A delegate to a method that will be used to compare keys. public OrderedDictionary(Comparison comparison) : this(null, Comparers.ComparerFromComparison(comparison), Comparers.ComparerKeyValueFromComparisonKey(comparison)) { } /// /// Creates a new OrderedDictionary. The TKey must implemented IComparable<TKey> /// or IComparable. /// The CompareTo method of this interface will be used to compare keys in this dictionary. /// A collection and keys and values (typically another dictionary) is used to initialized the /// contents of the dictionary. /// /// A collection of keys and values whose contents are used to initialized the dictionary. /// TKey does not implement IComparable<TKey>. public OrderedDictionary(IEnumerable> keysAndValues) : this(keysAndValues, Comparers.DefaultComparer()) { } /// /// Creates a new OrderedDictionary. The Compare method of the passed comparison object /// will be used to compare keys in this dictionary. /// A collection and keys and values (typically another dictionary) is used to initialized the /// contents of the dictionary. /// /// /// The GetHashCode and Equals methods of the provided IComparer<TKey> will never /// be called, and need not be implemented. /// A collection of keys and values whose contents are used to initialized the dictionary. /// An instance of IComparer<TKey> that will be used to compare keys. public OrderedDictionary(IEnumerable> keysAndValues, IComparer comparer) : this(keysAndValues, comparer, Comparers.ComparerKeyValueFromComparerKey(comparer)) { if (comparer == null) throw new ArgumentNullException("comparer"); } /// /// Creates a new OrderedDictionary. The passed delegate will be used to compare keys in this dictionary. /// A collection and keys and values (typically another dictionary) is used to initialized the /// contents of the dictionary. /// /// A collection of keys and values whose contents are used to initialized the dictionary. /// A delegate to a method that will be used to compare keys. public OrderedDictionary(IEnumerable> keysAndValues, Comparison comparison) : this(keysAndValues, Comparers.ComparerFromComparison(comparison), Comparers.ComparerKeyValueFromComparisonKey(comparison)) { } /// /// Creates a new OrderedDictionary. The passed comparer /// will be used to compare key-value pairs in this dictionary. Used internally /// from other constructors. /// /// A collection of keys and values whose contents are used to initialized the dictionary. /// An IComparer that will be used to compare keys. /// An IComparer that will be used to compare key-value pairs. private OrderedDictionary(IEnumerable> keysAndValues, IComparer keyComparer, IComparer> pairComparer) { this.keyComparer = keyComparer; this.pairComparer = pairComparer; tree = new RedBlackTree>(this.pairComparer); if (keysAndValues != null) AddMany(keysAndValues); } /// /// Creates a new OrderedDictionary. The passed comparison delegate /// will be used to compare keys in this dictionary, and the given tree is used. Used internally for Clone(). /// /// An IComparer that will be used to compare keys. /// A delegate to a method that will be used to compare key-value pairs. /// RedBlackTree that contains the data for the dictionary. private OrderedDictionary(IComparer keyComparer, IComparer> pairComparer, RedBlackTree> tree) { this.keyComparer = keyComparer; this.pairComparer = pairComparer; this.tree = tree; } /// /// 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 keys in the dictionary. /// The cloned dictionary. public OrderedDictionary Clone() { OrderedDictionary newDict = new OrderedDictionary(keyComparer, pairComparer, tree.Clone()); return newDict; } /// /// 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 keys in the dictionary. /// The cloned dictionary. /// TKey or TValue is a reference type that does not implement ICloneable. public OrderedDictionary 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)); OrderedDictionary newDict = new OrderedDictionary(null, keyComparer, pairComparer); foreach (KeyValuePair pair in tree) { // Clone the key and value parts of the pair. Value types can be cloned // by just copying them, otherwise, ICloneable is used. TKey keyClone; TValue valueClone; if (keyIsValueType) keyClone = pair.Key; else { if (pair.Key == null) keyClone = default(TKey); // Really null, because we know TKey isn't a value type. else keyClone = (TKey)(((ICloneable)pair.Key).Clone()); } if (valueIsValueType) valueClone = pair.Value; else { if (pair.Value == null) valueClone = default(TValue); // Really null, because we know TKey isn't a value type. else valueClone = (TValue)(((ICloneable)pair.Value).Clone()); } newDict.Add(keyClone, valueClone); } return newDict; } /// /// Returns the IComparer<T> used to compare keys in this dictionary. /// /// If the dictionary was created using a comparer, that comparer is returned. If the dictionary was /// created using a comparison delegate, then a comparer equivalent to that delegate /// is returned. Otherwise /// the default comparer for TKey (Comparer<TKey>.Default) is returned. public IComparer Comparer { get { return this.keyComparer; } } /// /// Returns a View collection that can be used for enumerating the keys and values in the collection in /// reversed order. /// /// ///

Typically, this method is used in conjunction with a foreach statement. For example: /// /// foreach(KeyValuePair<TKey, TValue> pair in dictionary.Reversed()) { /// // process pair /// } ///

///

If an entry is added to or deleted from the dictionary while the View is being enumerated, then /// the enumeration will end with an InvalidOperationException.

///

Calling Reverse does not copy the data in the dictionary, and the operation takes constant time.

///
/// An OrderedDictionary.View of key-value pairs in reverse order. public View Reversed() { return new View(this, tree.EntireRangeTester, true, true); } /// /// Returns a collection that can be used for enumerating some of the keys and values in the collection. /// Only keys that are greater than and /// less than are included. The keys are enumerated in sorted order. /// Keys equal to the end points of the range can be included or excluded depending on the /// and parameters. /// /// ///

If is greater than or equal to , the returned collection is empty.

///

The sorted order of the keys is determined by the comparison instance or delegate used /// to create the dictionary.

///

Typically, this property is used in conjunction with a foreach statement. For example:

/// /// foreach(KeyValuePair<TKey, TValue> pair in dictionary.Range(from, true, to, false)) { /// // process pair /// } /// ///

Calling Range does not copy the data in the dictionary, and the operation takes constant time.

/// The lower bound of the range. /// If true, the lower bound is inclusive--keys equal to the lower bound will /// be included in the range. If false, the lower bound is exclusive--keys equal to the lower bound will not /// be included in the range. /// The upper bound of the range. /// If true, the upper bound is inclusive--keys equal to the upper bound will /// be included in the range. If false, the upper bound is exclusive--keys equal to the upper bound will not /// be included in the range. /// An OrderedDictionary.View of key-value pairs in the given range. public View Range(TKey from, bool fromInclusive, TKey to, bool toInclusive) { return new View(this, tree.DoubleBoundedRangeTester(NewPair(from), fromInclusive, NewPair(to), toInclusive), false, false); } /// /// Returns a collection that can be used for enumerating some of the keys and values in the collection. /// Only keys that are greater than (and optionally, equal to) are included. /// The keys are enumerated in sorted order. Keys equal to can be included /// or excluded depending on the parameter. /// /// ///

The sorted order of the keys is determined by the comparison instance or delegate used /// to create the dictionary.

///

Typically, this property is used in conjunction with a foreach statement. For example:

/// /// foreach(KeyValuePair<TKey, TValue> pair in dictionary.RangeFrom(from, true)) { /// // process pair /// } /// ///

Calling RangeFrom does not copy of the data in the dictionary, and the operation takes constant time.

///
/// The lower bound of the range. /// If true, the lower bound is inclusive--keys equal to the lower bound will /// be included in the range. If false, the lower bound is exclusive--keys equal to the lower bound will not /// be included in the range. /// An OrderedDictionary.View of key-value pairs in the given range. public View RangeFrom(TKey from, bool fromInclusive) { return new View(this, tree.LowerBoundedRangeTester(NewPair(from), fromInclusive), false, false); } /// /// Returns a collection that can be used for enumerating some of the keys and values in the collection. /// Only items that are less than (and optionally, equal to) are included. /// The items are enumerated in sorted order. Items equal to can be included /// or excluded depending on the parameter. /// /// ///

The sorted order of the keys is determined by the comparison instance or delegate used /// to create the dictionary.

///

Typically, this property is used in conjunction with a foreach statement. For example:

/// /// foreach(KeyValuePair<TKey, TValue> pair in dictionary.RangeFrom(from, false)) { /// // process pair /// } /// ///

Calling RangeTo does not copy the data in the dictionary, and the operation takes constant time.

///
/// The upper bound of the range. /// If true, the upper bound is inclusive--keys equal to the upper bound will /// be included in the range. If false, the upper bound is exclusive--keys equal to the upper bound will not /// be included in the range. /// An OrderedDictionary.View of key-value pairs in the given range. public View RangeTo(TKey to, bool toInclusive) { return new View(this, tree.UpperBoundedRangeTester(NewPair(to), toInclusive), false, false); } #region IDictionary Members /// /// Removes the key (and associated value) from the collection that is equal to the passed in key. If /// no key in the dictionary is equal to the passed key, false is returned and the /// dictionary is unchanged. /// /// Equality between keys is determined by the comparison instance or delegate used /// to create the dictionary. /// The key to remove. /// True if the key was found and removed. False if the key was not found. public sealed override bool Remove(TKey key) { KeyValuePair keyPair = NewPair(key); KeyValuePair item; return tree.Delete(keyPair, true, out item); } /// /// Removes all keys and values from the dictionary. /// /// Clearing the dictionary takes a constant amount of time, regardless of the number of keys in it. public sealed override void Clear() { tree.StopEnumerations(); // Invalidate any enumerations. // The simplest and fastest way is simply to throw away the old tree and create a new one. tree = new RedBlackTree>(pairComparer); } /// /// Finds a key in the dictionary. If the dictionary already contains /// a key equal to the passed key, then the existing value is returned via value. If the dictionary /// doesn't contain that key, then value is associated with that key. /// /// between keys is determined by the comparison instance or delegate used /// to create the dictionary. /// This method takes time O(log N), where N is the number of keys in the dictionary. If a value is added, It is more efficient than /// calling TryGetValue followed by Add, because the dictionary is not searched twice. /// The new key. /// The new value to associated with that key, if the key isn't present. If the key was present, /// returns the exist value associated with that key. /// True if key was already present, false if key wasn't present (and a new value was added). public bool GetValueElseAdd(TKey key, ref TValue value) { KeyValuePair pair = NewPair(key, value); KeyValuePair old; bool added = tree.Insert(pair, DuplicatePolicy.DoNothing, out old); if (!added) value = old.Value; return !added; } /// /// Adds a new key and value to the dictionary. If the dictionary already contains /// a key equal to the passed key, then an ArgumentException is thrown /// /// /// Equality between keys is determined by the comparison instance or delegate used /// to create the dictionary. /// Adding an key and value takes time O(log N), where N is the number of keys in the dictionary. /// The new key. "null" is a valid key value. /// The new value to associated with that key. /// key is already present in the dictionary public sealed override void Add(TKey key, TValue value) { KeyValuePair pair = NewPair(key, value); KeyValuePair dummy; bool added = tree.Insert(pair, DuplicatePolicy.DoNothing, out dummy); if (! added) throw new ArgumentException(Strings.KeyAlreadyPresent, "key"); } /// /// Changes the value associated with a given key. If the dictionary does not contain /// a key equal to the passed key, then an ArgumentException is thrown. /// /// ///

Unlike adding or removing an element, changing the value associated with a key /// can be performed while an enumeration (foreach) on the the dictionary is in progress.

///

Equality between keys is determined by the comparison instance or delegate used /// to create the dictionary.

///

Replace takes time O(log N), where N is the number of entries in the dictionary.

/// The new key. /// The new value to associated with that key. /// key is not present in the dictionary public void Replace(TKey key, TValue value) { KeyValuePair pair = NewPair(key, value); KeyValuePair dummy; bool found = tree.Find(pair, true, true, out dummy); if (!found) throw new KeyNotFoundException(Strings.KeyNotFound); } /// /// Adds multiple key-value pairs to a dictionary. If a key exists in both the current instance and dictionaryToAdd, /// then the value is updated with the value from (no exception is thrown). /// Since IDictionary<TKey,TValue> inherits from IEnumerable<KeyValuePair<TKey,TValue>>, this /// method can be used to merge one dictionary into another. /// /// AddMany takes time O(M log (N+M)), where M is the size of , and N is the size of /// this dictionary. /// A collection of keys and values whose contents are added to the current dictionary. public void AddMany(IEnumerable> keysAndValues) { if (keysAndValues == null) throw new ArgumentNullException("keysAndValues"); foreach (KeyValuePair pair in keysAndValues) { this[pair.Key] = pair.Value; } } /// /// Removes all the keys found in another collection (such as an array or List<TKey>). Each key in keyCollectionToRemove /// is removed from the dictionary. Keys that are not present are ignored. /// /// RemoveMany takes time O(M log N), where M is the size of keyCollectionToRemove, and N is this /// size of this collection. /// The number of keys removed from the dictionary. /// A collection of keys to remove from the dictionary. public int RemoveMany(IEnumerable keyCollectionToRemove) { if (keyCollectionToRemove == null) throw new ArgumentNullException("keyCollectionToRemove"); int count = 0; foreach (TKey key in keyCollectionToRemove) { if (this.Remove(key)) ++count; } return count; } /// /// Gets or sets the value associated with a given key. When getting a value, if this /// key is not found in the collection, then an ArgumentException is thrown. When setting /// a value, the value replaces any existing value in the dictionary. /// /// The indexer takes time O(log N), where N is the number of entries in the dictionary. /// The value associated with the key /// A value is being retrieved, and the key is not present in the dictionary. /// is null. public sealed override TValue this[TKey key] { get { KeyValuePair pairFound; bool found; found = tree.Find(NewPair(key), false, false, out pairFound); if (found) return pairFound.Value; else throw new KeyNotFoundException(Strings.KeyNotFound); } set { KeyValuePair dummy; tree.Insert(NewPair(key, value), DuplicatePolicy.ReplaceLast, out dummy); } } /// /// Determines if this dictionary contains a key equal to . The dictionary /// is not changed. /// /// Searching the dictionary for a key takes time O(log N), where N is the number of keys in the dictionary. /// The key to search for. /// True if the dictionary contains key. False if the dictionary does not contain key. public sealed override bool ContainsKey(TKey key) { KeyValuePair pairFound; return tree.Find(NewPair(key), false, false, out pairFound); } /// /// Determines if this dictionary contains a key equal to . If so, the value /// associated with that key is returned through the value parameter. /// /// TryGetValue takes time O(log N), where N is the number of entries in the dictionary. /// The key to search for. /// Returns the value associated with key, if true was returned. /// True if the dictionary contains key. False if the dictionary does not contain key. public sealed override bool TryGetValue(TKey key, out TValue value) { KeyValuePair pairFound; bool found = tree.Find(NewPair(key), false, false, out pairFound); value = pairFound.Value; return found; } #endregion #region ICollection> Members /// /// Returns the number of keys in the dictionary. /// /// The size of the dictionary is returned in constant time.. /// The number of keys in the dictionary. public sealed override int Count { get { return tree.ElementCount; } } #endregion #region IEnumerable> Members /// /// Returns an enumerator that enumerates all the entries in the dictionary. Each entry is /// returned as a KeyValuePair<TKey,TValue>. /// The entries are enumerated in the sorted order of the keys. /// /// ///

Typically, this method is not called directly. Instead the "foreach" statement is used /// to enumerate the elements of the dictionary, which uses this method implicitly.

///

If an element is added to or deleted from the dictionary while it is being enumerated, then /// the enumeration will end with an InvalidOperationException.

///

Enumeration all the entries in the dictionary takes time O(N log N), where N is the number /// of entries in the dictionary.

///
/// An enumerator for enumerating all the elements in the OrderedDictionary. public sealed override IEnumerator> GetEnumerator() { return tree.GetEnumerator(); } #endregion #region ICloneable Members /// /// 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(); } #endregion /// /// The OrderedDictionary<TKey,TValue>.View class is used to look at a subset of the keys and values /// inside an ordered dictionary. It is returned from the Range, RangeTo, RangeFrom, and Reversed methods. /// /// ///

Views are dynamic. If the underlying dictionary changes, the view changes in sync. If a change is made /// to the view, the underlying dictionary changes accordingly.

///

Typically, this class is used in conjunction with a foreach statement to enumerate the keys /// and values in a subset of the OrderedDictionary. For example:

/// /// foreach(KeyValuePair<TKey, TValue> pair in dictionary.Range(from, to)) { /// // process pair /// } /// ///
[Serializable] public class View : DictionaryBase { private OrderedDictionary myDictionary; private RedBlackTree>.RangeTester rangeTester; // range tester for the range being used. private bool entireTree; // is the view the whole tree? private bool reversed; // is the view reversed? /// /// Initialize the View. /// /// Associated OrderedDictionary to be viewed. /// Range tester that defines the range being used. /// If true, then rangeTester defines the entire tree. /// Is the view enuemerated in reverse order? internal View(OrderedDictionary myDictionary, RedBlackTree>.RangeTester rangeTester, bool entireTree, bool reversed) { this.myDictionary = myDictionary; this.rangeTester = rangeTester; this.entireTree = entireTree; this.reversed = reversed; } /// /// Determine if the given key lies within the bounds of this view. /// /// Key to test. /// True if the key is within the bounds of this view. private bool KeyInView(TKey key) { return rangeTester(NewPair(key, default(TValue))) == 0; } /// /// Enumerate all the keys and values in this view. /// /// An IEnumerator of KeyValuePairs with the keys and views in this view. public sealed override IEnumerator> GetEnumerator() { if (reversed) return myDictionary.tree.EnumerateRangeReversed(rangeTester).GetEnumerator(); else return myDictionary.tree.EnumerateRange(rangeTester).GetEnumerator(); } /// /// Number of keys in this view. /// /// Number of keys that lie within the bounds the view. public sealed override int Count { get { if (entireTree) return myDictionary.Count; else return myDictionary.tree.CountRange(rangeTester); } } /// /// Tests if the key is present in the part of the dictionary being viewed. /// /// Key to check for. /// True if the key is within this view. public sealed override bool ContainsKey(TKey key) { if (!KeyInView(key)) return false; else return myDictionary.ContainsKey(key); } /// /// Determines if this view contains a key equal to . If so, the value /// associated with that key is returned through the value parameter. /// /// The key to search for. /// Returns the value associated with key, if true was returned. /// True if the key is within this view. public sealed override bool TryGetValue(TKey key, out TValue value) { if (!KeyInView(key)) { value = default(TValue); return false; } else { return myDictionary.TryGetValue(key, out value); } } /// /// Gets or sets the value associated with a given key. When getting a value, if this /// key is not found in the collection, then an ArgumentException is thrown. When setting /// a value, the value replaces any existing value in the dictionary. When setting a value, the /// key must be within the range of keys being viewed. /// /// The value associated with the key. /// A value is being retrieved, and the key is not present in the dictionary, /// or a value is being set, and the key is outside the range of keys being viewed by this View. public sealed override TValue this[TKey key] { get // technically we don't need to override this, but fixes a bug in NDOC. { return base[key]; } set { if (!KeyInView(key)) throw new ArgumentException(Strings.OutOfViewRange, "key"); else myDictionary[key] = value; } } /// /// Removes the key (and associated value) from the underlying dictionary of this view. that is equal to the passed in key. If /// no key in the view is equal to the passed key, the dictionary and view are unchanged. /// /// The key to remove. /// True if the key was found and removed. False if the key was not found. public sealed override bool Remove(TKey key) { if (!KeyInView(key)) return false; else return myDictionary.Remove(key); } /// /// Removes all the keys and values within this view from the underlying OrderedDictionary. /// /// The following removes all the keys that start with "A" from an OrderedDictionary. /// /// dictionary.Range("A", "B").Clear(); /// /// public sealed override void Clear() { if (entireTree) { myDictionary.Clear(); } else { myDictionary.tree.DeleteRange(rangeTester); } } /// /// Creates a new View that has the same keys and values as this, in the reversed order. /// /// A new View that has the reversed order of this view. public View Reversed() { return new View(myDictionary, rangeTester, entireTree, !reversed); } } } }