//****************************** // 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.Generic; using System.Collections; namespace Wintellect.PowerCollections { /// /// OrderedSet<T> is a collection that contains items of type T. /// The item are maintained in a sorted order, and duplicate items are not allowed. Each item has /// an index in the set: the smallest item has index 0, the next smallest item has index 1, /// and so forth. /// /// ///

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

///

OrderedSet 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 items in sorted order.

///
/// [Serializable] public class OrderedSet : CollectionBase, ICollection, ICloneable { // The comparer used to compare items. private IComparer comparer; // The red-black tree that actually does the work of storing the items. private RedBlackTree tree; #region Constructors /// /// Creates a new OrderedSet. The T must implement IComparable<T> /// or IComparable. /// The CompareTo method of this interface will be used to compare items in this set. /// /// /// Items that are null are permitted, and will be sorted before all other items. /// /// T does not implement IComparable<TKey>. public OrderedSet(): this(Comparers.DefaultComparer()) { } /// /// Creates a new OrderedSet. The passed delegate will be used to compare items in this set. /// /// A delegate to a method that will be used to compare items. public OrderedSet(Comparison comparison) : this(Comparers.ComparerFromComparison(comparison)) { } /// /// Creates a new OrderedSet. The Compare method of the passed comparison object /// will be used to compare items in this set. /// /// /// The GetHashCode and Equals methods of the provided IComparer<T> will never /// be called, and need not be implemented. /// /// An instance of IComparer<T> that will be used to compare items. public OrderedSet(IComparer comparer) { if (comparer == null) throw new ArgumentNullException("comparer"); this.comparer = comparer; tree = new RedBlackTree(comparer); } /// /// Creates a new OrderedSet. The T must implement IComparable<T> /// or IComparable. /// The CompareTo method of this interface will be used to compare items in this set. The set is /// initialized with all the items in the given collection. /// /// /// Items that are null are permitted, and will be sorted before all other items. /// /// A collection with items to be placed into the OrderedSet. /// T does not implement IComparable<TKey>. public OrderedSet(IEnumerable collection): this(collection, Comparers.DefaultComparer()) { } /// /// Creates a new OrderedSet. The passed delegate will be used to compare items in this set. /// The set is initialized with all the items in the given collection. /// /// A collection with items to be placed into the OrderedSet. /// A delegate to a method that will be used to compare items. public OrderedSet(IEnumerable collection, Comparison comparison): this(collection, Comparers.ComparerFromComparison(comparison)) { } /// /// Creates a new OrderedSet. The Compare method of the passed comparison object /// will be used to compare items in this set. The set is /// initialized with all the items in the given collection. /// /// /// The GetHashCode and Equals methods of the provided IComparer<T> will never /// be called, and need not be implemented. /// /// A collection with items to be placed into the OrderedSet. /// An instance of IComparer<T> that will be used to compare items. public OrderedSet(IEnumerable collection, IComparer comparer): this(comparer) { AddMany(collection); } /// /// Creates a new OrderedSet given a comparer and a tree that contains the data. Used /// internally for Clone. /// /// Comparer for the set. /// Data for the set. private OrderedSet(IComparer comparer, RedBlackTree tree) { this.comparer = comparer; this.tree = tree; } #endregion Constructors #region Cloning /// /// Makes a shallow clone of this set; i.e., if items of the /// set are reference types, then they are not cloned. If T is a value type, /// then each element is copied as if by simple assignment. /// /// Cloning the set takes time O(N), where N is the number of items in the set. /// The cloned set. object ICloneable.Clone() { return this.Clone(); } /// /// Makes a shallow clone of this set; i.e., if items of the /// set are reference types, then they are not cloned. If T is a value type, /// then each element is copied as if by simple assignment. /// /// Cloning the set takes time O(N), where N is the number of items in the set. /// The cloned set. public OrderedSet Clone() { OrderedSet newSet = new OrderedSet(comparer, tree.Clone()); return newSet; } /// /// Makes a deep clone of this set. A new set is created with a clone of /// each element of this set, by calling ICloneable.Clone on each element. If T is /// a value type, then each element is copied as if by simple assignment. /// /// If T is a reference type, it must implement /// ICloneable. Otherwise, an InvalidOperationException is thrown. /// Cloning the set takes time O(N log N), where N is the number of items in the set. /// The cloned set. /// T is a reference type that does not implement ICloneable. public OrderedSet CloneContents() { bool itemIsValueType; if (!Util.IsCloneableType(typeof(T), out itemIsValueType)) throw new InvalidOperationException(string.Format(Strings.TypeNotCloneable, typeof(T).FullName)); OrderedSet clone = new OrderedSet(comparer); // Clone each item, and add it to the new ordered set. foreach (T item in this) { T itemClone; if (itemIsValueType) itemClone = item; else { if (item == null) itemClone = default(T); // Really null, because we know T is a reference type else itemClone = (T)(((ICloneable)item).Clone()); } clone.Add(itemClone); } return clone; } #endregion Cloning #region Basic collection containment /// /// Returns the IComparer<T> used to compare items in this set. /// /// If the set was created using a comparer, that comparer is returned. If the set was /// created using a comparison delegate, then a comparer equivalent to that delegate /// is returned. Otherwise /// the default comparer for T (Comparer<T>.Default) is returned. public IComparer Comparer { get { return this.comparer; } } /// /// Returns the number of items in the set. /// /// The size of the set is returned in constant time. /// The number of items in the set. public sealed override int Count { get { return tree.ElementCount; } } /// /// Returns an enumerator that enumerates all the items in the set. /// The items are enumerated in sorted order. /// /// ///

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

///

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

///

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

///
/// An enumerator for enumerating all the items in the OrderedSet. public sealed override IEnumerator GetEnumerator() { return tree.GetEnumerator(); } /// /// Determines if this set contains an item equal to . The set /// is not changed. /// /// Searching the set for an item takes time O(log N), where N is the number of items in the set. /// The item to search for. /// True if the set contains . False if the set does not contain . public sealed override bool Contains(T item) { T dummy; return tree.Find(item, false, false, out dummy); } /// /// Determines if this set contains an item equal to , according to the /// comparison mechanism that was used when the set was created. The set /// is not changed. /// If the set does contain an item equal to , then the item from the set is returned. /// /// Searching the set for an item takes time O(log N), where N is the number of items in the set. /// /// In the following example, the set contains strings which are compared in a case-insensitive manner. /// /// OrderedSet<string> set = new OrderedSet<string>(StringComparer.CurrentCultureIgnoreCase); /// set.Add("HELLO"); /// string s; /// bool b = set.TryGetItem("Hello", out s); // b receives true, s receives "HELLO". /// /// /// The item to search for. /// Returns the item from the set that was equal to . /// True if the set contains . False if the set does not contain . public bool TryGetItem(T item, out T foundItem) { return tree.Find(item, true, false, out foundItem); } #endregion #region Index by sorted order /// /// Get the item by its index in the sorted order. The smallest item has index 0, /// the next smallest item has index 1, and the largest item has index Count-1. /// /// The indexer takes time O(log N), which N is the number of items in /// the set. /// The index to get the item by. /// The item at the given index. /// is /// less than zero or greater than or equal to Count. public T this[int index] { get { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException("index"); return tree.GetItemByIndex(index); } } /// /// Get the index of the given item in the sorted order. The smallest item has index 0, /// the next smallest item has index 1, and the largest item has index Count-1. /// /// Finding the index takes time O(log N), which N is the number of items in /// the set. /// The item to get the index of. /// The index of the item in the sorted set, or -1 if the item is not present /// in the set. public int IndexOf(T item) { return tree.FindIndex(item, true); } #endregion #region Adding elements /// /// Adds a new item to the set. If the set already contains an item equal to /// , that item is replaced with . /// /// /// Equality between items is determined by the comparison instance or delegate used /// to create the set. /// Adding an item takes time O(log N), where N is the number of items in the set. /// The item to add to the set. /// True if the set already contained an item equal to (which was replaced), false /// otherwise. public new bool Add(T item) { T dummy; return ! tree.Insert(item, DuplicatePolicy.ReplaceFirst, out dummy); } /// /// Adds a new item to the set. If the set already contains an item equal to /// , that item is replaces with . /// /// /// Equality between items is determined by the comparison instance or delegate used /// to create the set. /// Adding an item takes time O(log N), where N is the number of items in the set. /// The item to add to the set. void ICollection.Add(T item) { Add(item); } /// /// Adds all the items in to the set. If the set already contains an item equal to /// one of the items in , that item will be replaced. /// /// /// Equality between items is determined by the comparison instance or delegate used /// to create the set. /// Adding the collection takes time O(M log N), where N is the number of items in the set, and M is the /// number of items in . /// A collection of items to add to the set. public void AddMany(IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); // If we're adding ourselves, then there is nothing to do. if (object.ReferenceEquals(collection, this)) return; foreach (T item in collection) Add(item); } #endregion Adding elements #region Removing elements /// /// Searches the set for an item equal to , and if found, /// removes it from the set. If not found, the set is unchanged. /// /// /// Equality between items is determined by the comparison instance or delegate used /// to create the set. /// Removing an item from the set takes time O(log N), where N is the number of items in the set. /// The item to remove. /// True if was found and removed. False if was not in the set. public sealed override bool Remove(T item) { T dummy; return tree.Delete(item, true, out dummy); } /// /// Removes all the items in from the set. Items /// not present in the set are ignored. /// /// /// Equality between items is determined by the comparison instance or delegate used /// to create the set. /// Removing the collection takes time O(M log N), where N is the number of items in the set, and M is the /// number of items in . /// A collection of items to remove from the set. /// The number of items removed from the set. /// is null. public int RemoveMany(IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); int count = 0; if (collection == this) { count = Count; Clear(); // special case, otherwise we will throw. } else { foreach (T item in collection) { if (Remove(item)) ++count; } } return count; } /// /// Removes all items from the set. /// /// Clearing the sets takes a constant amount of time, regardless of the number of items 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(comparer); } #endregion Removing elements #region First/last items /// /// If the collection is empty, throw an invalid operation exception. /// /// The set is empty. private void CheckEmpty() { if (Count == 0) throw new InvalidOperationException(Strings.CollectionIsEmpty); } /// /// Returns the first item in the set: the item /// that would appear first if the set was enumerated. This is also /// the smallest item in the set. /// /// GetFirst() takes time O(log N), where N is the number of items in the set. /// The first item in the set. /// The set is empty. public T GetFirst() { T item; CheckEmpty(); tree.FirstItemInRange(tree.EntireRangeTester, out item); return item; } /// /// Returns the lastl item in the set: the item /// that would appear last if the set was enumerated. This is also the /// largest item in the set. /// /// GetLast() takes time O(log N), where N is the number of items in the set. /// The lastl item in the set. /// The set is empty. public T GetLast() { T item; CheckEmpty(); tree.LastItemInRange(tree.EntireRangeTester, out item); return item; } /// /// Removes the first item in the set. This is also the smallest item in the set. /// /// RemoveFirst() takes time O(log N), where N is the number of items in the set. /// The item that was removed, which was the smallest item in the set. /// The set is empty. public T RemoveFirst() { CheckEmpty(); T item; tree.DeleteItemFromRange(tree.EntireRangeTester, true, out item); return item; } /// /// Removes the last item in the set. This is also the largest item in the set. /// /// RemoveLast() takes time O(log N), where N is the number of items in the set. /// The item that was removed, which was the largest item in the set. /// The set is empty. public T RemoveLast() { CheckEmpty(); T item; tree.DeleteItemFromRange(tree.EntireRangeTester, false, out item); return item; } #endregion #region Set operations /// /// Check that this set and another set were created with the same comparison /// mechanism. Throws exception if not compatible. /// /// Other set to check comparision mechanism. /// If otherSet and this set don't use the same method for comparing items. private void CheckConsistentComparison(OrderedSet otherSet) { if (otherSet == null) throw new ArgumentNullException("otherSet"); if (!object.Equals(comparer, otherSet.comparer)) throw new InvalidOperationException(Strings.InconsistentComparisons); } /// /// Determines if this set is a superset of another set. Neither set is modified. /// This set is a superset of if every element in /// is also in this set. /// IsSupersetOf is computed in time O(M log N), where M is the size of the /// , and N is the size of the this set. /// /// OrderedSet to compare to. /// True if this is a superset of . /// This set and don't use the same method for comparing items. public bool IsSupersetOf(OrderedSet otherSet) { CheckConsistentComparison(otherSet); if (otherSet.Count > this.Count) return false; // Can't be a superset of a bigger set // Check each item in the other set to make sure it is in this set. foreach (T item in otherSet) { if (!this.Contains(item)) return false; } return true; } /// /// Determines if this set is a proper superset of another set. Neither set is modified. /// This set is a proper superset of if every element in /// is also in this set. /// Additionally, this set must have strictly more items than . /// /// IsProperSupersetOf is computed in time O(M log N), where M is the number of unique items in /// . /// OrderedSet to compare to. /// True if this is a proper superset of . /// This set and don't use the same method for comparing items. public bool IsProperSupersetOf(OrderedSet otherSet) { CheckConsistentComparison(otherSet); if (otherSet.Count >= this.Count) return false; // Can't be a proper superset of a bigger or equal set return IsSupersetOf(otherSet); } /// /// Determines if this set is a subset of another set. Neither set is modified. /// This set is a subset of if every element in this set /// is also in . /// /// IsSubsetOf is computed in time O(N log M), where M is the size of the /// , and N is the size of the this set. /// Set to compare to. /// True if this is a subset of . /// This set and don't use the same method for comparing items. public bool IsSubsetOf(OrderedSet otherSet) { return otherSet.IsSupersetOf(this); } /// /// Determines if this set is a proper subset of another set. Neither set is modified. /// This set is a subset of if every element in this set /// is also in . Additionally, this set must have strictly /// fewer items than . /// /// IsSubsetOf is computed in time O(N log M), where M is the size of the /// , and N is the size of the this set. /// Set to compare to. /// True if this is a proper subset of . /// This set and don't use the same method for comparing items. public bool IsProperSubsetOf(OrderedSet otherSet) { return otherSet.IsProperSupersetOf(this); } /// /// Determines if this set is equal to another set. This set is equal to /// if they contain the same items. /// /// IsEqualTo is computed in time O(N), where N is the number of items in /// this set. /// Set to compare to /// True if this set is equal to , false otherwise. /// This set and don't use the same method for comparing items. public bool IsEqualTo(OrderedSet otherSet) { CheckConsistentComparison(otherSet); // Must be the same size. if (otherSet.Count != this.Count) return false; // Since both sets are ordered, we can simply compare items in order. using (IEnumerator enum1 = this.GetEnumerator(), enum2 = otherSet.GetEnumerator()) { bool continue1, continue2; for (; ; ) { continue1 = enum1.MoveNext(); continue2 = enum2.MoveNext(); if (!continue1 || !continue2) break; if (comparer.Compare(enum1.Current, enum2.Current) != 0) return false; // the two items are not equal. } // If both continue1 and continue2 are false, we reached the end of both sequences at the same // time and found success. If one is true and one is false, the sequences were of difference lengths -- failure. return (continue1 == continue2); } } /// /// Computes the union of this set with another set. The union of two sets /// is all items that appear in either or both of the sets. This set receives /// the union of the two sets, the other set is unchanged. /// /// /// If equal items appear in both sets, the union will include an arbitrary choice of one of the /// two equal items. /// The union of two sets is computed in time O(M + N log M), where M is the size of the /// larger set, and N is the size of the smaller set. /// /// Set to union with. /// This set and don't use the same method for comparing items. public void UnionWith(OrderedSet otherSet) { CheckConsistentComparison(otherSet); AddMany(otherSet); // CONSIDER: if RedBlackTree cloning is O(N), then if otherSet is much larger, better to clone it, // add all of the current into it, and replace. } /// /// Determines if this set is disjoint from another set. Two sets are disjoint /// if no item from one set is equal to any item in the other set. /// /// /// The answer is computed in time O(N log M), where M is the size of the /// larger set, and N is the size of the smaller set. /// /// Set to check disjointness with. /// True if the two sets are disjoint, false otherwise. /// This set and don't use the same method for comparing items. public bool IsDisjointFrom(OrderedSet otherSet) { CheckConsistentComparison(otherSet); OrderedSet smaller, larger; if (otherSet.Count > this.Count) { smaller = this; larger = otherSet; } else { smaller = otherSet; larger = this; } foreach (T item in smaller) { if (larger.Contains(item)) return false; } return true; } /// /// Computes the union of this set with another set. The union of two sets /// is all items that appear in either or both of the sets. A new set is /// created with the union of the sets and is returned. This set and the other set /// are unchanged. /// /// /// If equal items appear in both sets, the union will include an arbitrary choice of one of the /// two equal items. /// The union of two sets is computed in time O(M + N log M), where M is the size of the /// larger set, and N is the size of the smaller set. /// /// Set to union with. /// The union of the two sets. /// This set and don't use the same method for comparing items. public OrderedSet Union(OrderedSet otherSet) { CheckConsistentComparison(otherSet); OrderedSet smaller, larger, result; if (otherSet.Count > this.Count) { smaller = this; larger = otherSet; } else { smaller = otherSet; larger = this; } result = larger.Clone(); result.AddMany(smaller); return result; } /// /// Computes the intersection of this set with another set. The intersection of two sets /// is all items that appear in both of the sets. This set receives /// the intersection of the two sets, the other set is unchanged. /// /// /// When equal items appear in both sets, the intersection will include an arbitrary choice of one of the /// two equal items. /// The intersection of two sets is computed in time O(N log M), where M is the size of the /// larger set, and N is the size of the smaller set. /// /// Set to intersection with. /// This set and don't use the same method for comparing items. public void IntersectionWith(OrderedSet otherSet) { CheckConsistentComparison(otherSet); tree.StopEnumerations(); OrderedSet smaller, larger; if (otherSet.Count > this.Count) { smaller = this; larger = otherSet; } else { smaller = otherSet; larger = this; } T dummy; RedBlackTree newTree = new RedBlackTree(comparer); foreach (T item in smaller) { if (larger.Contains(item)) newTree.Insert(item, DuplicatePolicy.ReplaceFirst, out dummy); } tree = newTree; } /// /// Computes the intersection of this set with another set. The intersection of two sets /// is all items that appear in both of the sets. A new set is /// created with the intersection of the sets and is returned. This set and the other set /// are unchanged. /// /// /// When equal items appear in both sets, the intersection will include an arbitrary choice of one of the /// two equal items. /// The intersection of two sets is computed in time O(N log M), where M is the size of the /// larger set, and N is the size of the smaller set. /// /// Set to intersection with. /// The intersection of the two sets. /// This set and don't use the same method for comparing items. public OrderedSet Intersection(OrderedSet otherSet) { CheckConsistentComparison(otherSet); OrderedSet smaller, larger, result; if (otherSet.Count > this.Count) { smaller = this; larger = otherSet; } else { smaller = otherSet; larger = this; } result = new OrderedSet(comparer); foreach (T item in smaller) { if (larger.Contains(item)) result.Add(item); } return result; } /// /// Computes the difference of this set with another set. The difference of these two sets /// is all items that appear in this set, but not in . This set receives /// the difference of the two sets; the other set is unchanged. /// /// /// The difference of two sets is computed in time O(M + N log M), where M is the size of the /// larger set, and N is the size of the smaller set. /// /// Set to difference with. /// This set and don't use the same method for comparing items. public void DifferenceWith(OrderedSet otherSet) { // Difference with myself is nothing. This check is needed because the // main algorithm doesn't work correctly otherwise. if (this == otherSet) Clear(); CheckConsistentComparison(otherSet); if (otherSet.Count < this.Count){ foreach (T item in otherSet) { this.Remove(item); } } else { RemoveAll(delegate(T item) { return otherSet.Contains(item); }); } } /// /// Computes the difference of this set with another set. The difference of these two sets /// is all items that appear in this set, but not in . A new set is /// created with the difference of the sets and is returned. This set and the other set /// are unchanged. /// /// /// The difference of two sets is computed in time O(M + N log M), where M is the size of the /// larger set, and N is the size of the smaller set. /// /// Set to difference with. /// The difference of the two sets. /// This set and don't use the same method for comparing items. public OrderedSet Difference(OrderedSet otherSet) { CheckConsistentComparison(otherSet); OrderedSet result = this.Clone(); result.DifferenceWith(otherSet); return result; } /// /// Computes the symmetric difference of this set with another set. The symmetric difference of two sets /// is all items that appear in either of the sets, but not both. This set receives /// the symmetric difference of the two sets; the other set is unchanged. /// /// /// The symmetric difference of two sets is computed in time O(M + N log M), where M is the size of the /// larger set, and N is the size of the smaller set. /// /// Set to symmetric difference with. /// This set and don't use the same method for comparing items. public void SymmetricDifferenceWith(OrderedSet otherSet) { // Symmetric difference with myself is nothing. This check is needed because the // main algorithm doesn't work correctly otherwise. if (this == otherSet) Clear(); CheckConsistentComparison(otherSet); // CONSIDER: if otherSet is larger, better to clone it and reverse the below? foreach (T item in otherSet) { if (this.Contains(item)) this.Remove(item); else this.Add(item); } } /// /// Computes the symmetric difference of this set with another set. The symmetric difference of two sets /// is all items that appear in either of the sets, but not both. A new set is /// created with the symmetric difference of the sets and is returned. This set and the other set /// are unchanged. /// /// /// The symmetric difference of two sets is computed in time O(M + N log M), where M is the size of the /// larger set, and N is the size of the smaller set. /// /// Set to symmetric difference with. /// The symmetric difference of the two sets. /// This set and don't use the same method for comparing items. public OrderedSet SymmetricDifference(OrderedSet otherSet) { CheckConsistentComparison(otherSet); OrderedSet smaller, larger, result; if (otherSet.Count > this.Count) { smaller = this; larger = otherSet; } else { smaller = otherSet; larger = this; } result = larger.Clone(); foreach (T item in smaller) { if (result.Contains(item)) result.Remove(item); else result.Add(item); } return result; } #endregion Set operations #region Read-only list view /// /// Get a read-only list view of the items in this ordered set. The /// items in the list are in sorted order, with the smallest item /// at index 0. This view does not copy any data, and reflects any /// changes to the underlying OrderedSet. /// /// A read-only IList<T> view onto this OrderedSet. public IList AsList() { return new ListView(this, tree.EntireRangeTester, true, false); } /// /// The nested class that provides a read-only list view /// of all or part of the collection. /// [Serializable] private class ListView : ReadOnlyListBase { private OrderedSet mySet; 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? /// /// Create a new list view wrapped the given set. /// /// /// Range tester that defines the range being used. /// If true, then rangeTester defines the entire tree. Used to optimize some operations. /// Is the view enuemerated in reverse order? public ListView(OrderedSet mySet, RedBlackTree.RangeTester rangeTester, bool entireTree, bool reversed) { this.mySet = mySet; this.rangeTester = rangeTester; this.entireTree = entireTree; this.reversed = reversed; } public override int Count { get { if (entireTree) return mySet.Count; else { // Note: we can't cache the result of this call because the underlying // set can change, which would make the cached value incorrect. return mySet.tree.CountRange(rangeTester); } } } public override T this[int index] { get { if (entireTree) { if (reversed) return mySet[mySet.Count - 1 - index]; else return mySet[index]; } else { T dummy; int firstIndex = mySet.tree.FirstItemInRange(rangeTester, out dummy); int lastIndex = mySet.tree.LastItemInRange(rangeTester, out dummy); if (firstIndex < 0 || lastIndex < 0 || index < 0 || index >= (lastIndex - firstIndex + 1)) throw new ArgumentOutOfRangeException("index"); if (reversed) return mySet[lastIndex - index]; else return mySet[firstIndex + index]; } } } public override int IndexOf(T item) { if (entireTree) { if (reversed) return mySet.Count - 1 - mySet.IndexOf(item); else return mySet.IndexOf(item); } else { T dummy; if (rangeTester(item) != 0) return -1; if (reversed) { int indexInSet = mySet.tree.FindIndex(item, false); if (indexInSet < 0) return -1; int indexOfEnd = mySet.tree.LastItemInRange(rangeTester, out dummy); return indexOfEnd - indexInSet; } else { int indexInSet = mySet.tree.FindIndex(item, true); if (indexInSet < 0) return -1; int indexOfStart = mySet.tree.FirstItemInRange(rangeTester, out dummy); return indexInSet - indexOfStart; } } } } #endregion Read-only list view #region Sub-views /// /// Returns a View collection that can be used for enumerating the items in the set in /// reversed order. /// /// ///

Typically, this method is used in conjunction with a foreach statement. For example: /// /// foreach(T item in set.Reversed()) { /// // process item /// } ///

///

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

///

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

///
/// An OrderedSet.View of items in reverse order. public View Reversed() // A reversed view that can be enumerated { return new View(this, tree.EntireRangeTester, true, true); } /// /// Returns a View collection that can be used for enumerating a range of the items in the set.. /// Only items that are greater than and /// less than are included. The items are enumerated in sorted order. /// Items equal to the end points of the range can be included or excluded depending on the /// and parameters. /// /// ///

If is greater than , the returned collection is empty.

///

Typically, this method is used in conjunction with a foreach statement. For example: /// /// foreach(T item in set.Range(from, true, to, false)) { /// // process item /// } ///

///

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

///

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

///
/// The lower bound of the range. /// If true, the lower bound is inclusive--items equal to the lower bound will /// be included in the range. If false, the lower bound is exclusive--items 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--items equal to the upper bound will /// be included in the range. If false, the upper bound is exclusive--items equal to the upper bound will not /// be included in the range. /// An OrderedSet.View of items in the given range. public View Range(T from, bool fromInclusive, T to, bool toInclusive) // A partial view that can be enumerated { return new View(this, tree.DoubleBoundedRangeTester(from, fromInclusive, to, toInclusive), false, false); } /// /// Returns a View collection that can be used for enumerating a range of the items in the set.. /// Only items that are greater 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. /// /// ///

Typically, this method is used in conjunction with a foreach statement. For example: /// /// foreach(T item in set.RangeFrom(from, true)) { /// // process item /// } ///

///

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

///

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

///
/// The lower bound of the range. /// If true, the lower bound is inclusive--items equal to the lower bound will /// be included in the range. If false, the lower bound is exclusive--items equal to the lower bound will not /// be included in the range. /// An OrderedSet.View of items in the given range. public View RangeFrom(T from, bool fromInclusive) // A partial view that can be enumerated { return new View(this, tree.LowerBoundedRangeTester(from, fromInclusive), false, false); } /// /// Returns a View collection that can be used for enumerating a range of the items in the set.. /// 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. /// /// ///

Typically, this method is used in conjunction with a foreach statement. For example: /// /// foreach(T item in set.RangeTo(to, false)) { /// // process item /// } ///

///

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

///

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

///
/// The upper bound of the range. /// If true, the upper bound is inclusive--items equal to the upper bound will /// be included in the range. If false, the upper bound is exclusive--items equal to the upper bound will not /// be included in the range. /// An OrderedSet.View of items in the given range. public View RangeTo(T to, bool toInclusive) // A partial view that can be enumerated { return new View(this, tree.UpperBoundedRangeTester(to, toInclusive), false, false); } #endregion #region View nested class /// /// The OrderedSet<T>.View class is used to look at a subset of the Items /// inside an ordered set. It is returned from the Range, RangeTo, RangeFrom, and Reversed methods. /// /// ///

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

///

Typically, this class is used in conjunction with a foreach statement to enumerate the items /// in a subset of the OrderedSet. For example:

/// /// foreach(T item in set.Range(from, to)) { /// // process item /// } /// ///
[Serializable] public class View : CollectionBase, ICollection { private OrderedSet mySet; 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. /// /// OrderedSet being viewed /// Range tester that defines the range being used. /// If true, then rangeTester defines the entire tree. Used to optimize some operations. /// Is the view enuemerated in reverse order? internal View(OrderedSet mySet, RedBlackTree.RangeTester rangeTester, bool entireTree, bool reversed) { this.mySet = mySet; this.rangeTester = rangeTester; this.entireTree = entireTree; this.reversed = reversed; } /// /// Determine if the given item lies within the bounds of this view. /// /// Item to test. /// True if the item is within the bounds of this view. private bool ItemInView(T item) { return rangeTester(item) == 0; } /// /// Enumerate all the items in this view. /// /// An IEnumerator<T> with the items in this view. public sealed override IEnumerator GetEnumerator() { if (reversed) return mySet.tree.EnumerateRangeReversed(rangeTester).GetEnumerator(); else return mySet.tree.EnumerateRange(rangeTester).GetEnumerator(); } /// /// Number of items in this view. /// /// Number of items that lie within the bounds the view. public sealed override int Count { get { if (entireTree) return mySet.Count; else { // Note: we can't cache the result of this call because the underlying // set can change, which would make the cached value incorrect. return mySet.tree.CountRange(rangeTester); } } } /// /// Removes all the items within this view from the underlying set. /// /// The following removes all the items that start with "A" from an OrderedSet. /// /// set.Range("A", "B").Clear(); /// /// public sealed override void Clear() { if (entireTree) { mySet.Clear(); // much faster than DeleteRange } else { mySet.tree.DeleteRange(rangeTester); } } /// /// Adds a new item to the set underlying this View. If the set already contains an item equal to /// , that item is replaces with . If /// is outside the range of this view, an InvalidOperationException /// is thrown. /// /// /// Equality between items is determined by the comparison instance or delegate used /// to create the set. /// Adding an item takes time O(log N), where N is the number of items in the set. /// The item to add. /// True if the set already contained an item equal to (which was replaced), false /// otherwise. public new bool Add(T item) { if (!ItemInView(item)) throw new ArgumentException(Strings.OutOfViewRange, "item"); else return mySet.Add(item); } /// /// Adds a new item to the set underlying this View. If the set already contains an item equal to /// , that item is replaces with . If /// is outside the range of this view, an InvalidOperationException /// is thrown. /// /// /// Equality between items is determined by the comparison instance or delegate used /// to create the set. /// Adding an item takes time O(log N), where N is the number of items in the set. /// The item to add. void ICollection.Add(T item) { Add(item); } /// /// Searches the underlying set for an item equal to , and if found, /// removes it from the set. If not found, the set is unchanged. If the item is outside /// the range of this view, the set is unchanged. /// /// /// Equality between items is determined by the comparison instance or delegate used /// to create the set. /// Removing an item from the set takes time O(log N), where N is the number of items in the set. /// The item to remove. /// True if was found and removed. False if was not in the set, or /// was outside the range of this view. public sealed override bool Remove(T item) { if (!ItemInView(item)) return false; else return mySet.Remove(item); } /// /// Determines if this view of the set contains an item equal to . The set /// is not changed. If /// /// Searching the set for an item takes time O(log N), where N is the number of items in the set. /// The item to search for. /// True if the set contains , and is within /// the range of this view. False otherwise. public sealed override bool Contains(T item) { if (!ItemInView(item)) return false; else return mySet.Contains(item); } /// /// Get the index of the given item in the view. The smallest item in the view has index 0, /// the next smallest item has index 1, and the largest item has index Count-1. /// /// Finding the index takes time O(log N), which N is the number of items in /// the set. /// The item to get the index of. /// The index of the item in the view, or -1 if the item is not present /// in the view. public int IndexOf(T item) { if (entireTree) { if (reversed) { int indexInSet = mySet.tree.FindIndex(item, false); if (indexInSet < 0) return -1; return mySet.Count - 1 - indexInSet; } else { return mySet.tree.FindIndex(item, true); } } else { T dummy; if (!ItemInView(item)) return -1; if (reversed) { int indexInSet = mySet.tree.FindIndex(item, false); if (indexInSet < 0) return -1; int indexOfEnd = mySet.tree.LastItemInRange(rangeTester, out dummy); return indexOfEnd - indexInSet; } else { int indexInSet = mySet.tree.FindIndex(item, true); if (indexInSet < 0) return -1; int indexOfStart = mySet.tree.FirstItemInRange(rangeTester, out dummy); return indexInSet - indexOfStart; } } } /// /// Get the item by its index in the sorted order. The smallest item in the view has index 0, /// the next smallest item has index 1, and the largest item has index Count-1. /// /// The indexer takes time O(log N), which N is the number of items in /// the set. /// The index to get the item by. /// The item at the given index. /// is /// less than zero or greater than or equal to Count. public T this[int index] { get { if (entireTree) { if (reversed) { return mySet[mySet.Count - 1 - index]; } else { return mySet[index]; } } else { T dummy; int firstIndex = mySet.tree.FirstItemInRange(rangeTester, out dummy); int lastIndex = mySet.tree.LastItemInRange(rangeTester, out dummy); if (firstIndex < 0 || lastIndex < 0 || index < 0 || index >= (lastIndex - firstIndex + 1)) throw new ArgumentOutOfRangeException("index"); if (reversed) return mySet[lastIndex - index]; else return mySet[firstIndex + index]; } } } /// /// Get a read-only list view of the items in this view. The /// items in the list are in sorted order, with the smallest item /// at index 0. This view does not copy any data, and reflects any /// changes to the underlying OrderedSet. /// /// A read-only IList<T> view onto this view. public IList AsList() { return new ListView(mySet, rangeTester, entireTree, reversed); } /// /// Creates a new View that has the same items as this view, in the reversed order. /// /// A new View that has the reversed order of this view, with the same upper /// and lower bounds. public View Reversed() { return new View(mySet, rangeTester, entireTree, !reversed); } /// /// Returns the first item in this view: the item /// that would appear first if the view was enumerated. /// /// GetFirst() takes time O(log N), where N is the number of items in the set. /// The first item in the view. /// The view has no items in it. public T GetFirst() { T item; int found; if (reversed) found = mySet.tree.LastItemInRange(rangeTester, out item); else found = mySet.tree.FirstItemInRange(rangeTester, out item); if (found < 0) throw new InvalidOperationException(Strings.CollectionIsEmpty); return item; } /// /// Returns the last item in the view: the item /// that would appear last if the view was enumerated. /// /// GetLast() takes time O(log N), where N is the number of items in the set. /// The last item in the view. /// The view has no items in it. public T GetLast() { T item; int found; if (reversed) found = mySet.tree.FirstItemInRange(rangeTester, out item); else found = mySet.tree.LastItemInRange(rangeTester, out item); if (found < 0) throw new InvalidOperationException(Strings.CollectionIsEmpty); return item; } } #endregion } }