//****************************** // 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; using System.Diagnostics; namespace Wintellect.PowerCollections { /// /// Set<T> is a collection that contains items of type T. /// The item are maintained in a haphazard, unpredictable order, and duplicate items are not allowed. /// /// ///

The items are compared in one of two ways. If T implements IComparable<T> /// then the Equals method of that interface will be used to compare items, otherwise the Equals /// method from Object will be used. Alternatively, an instance of IComparer<T> can be passed /// to the constructor to use to compare items.

///

Set is implemented as a hash table. Inserting, deleting, and looking up an /// an element all are done in approximately constant time, regardless of the number of items in the Set.

///

is similar, but uses comparison instead of hashing, and does maintains /// the items in sorted order.

///
/// [Serializable] public class Set : CollectionBase, ICollection, ICloneable { // The comparer used to hash/compare items. private IEqualityComparer equalityComparer; // The hash table that actually does the work of storing the items. private Hash hash; #region Constructors /// /// Creates a new Set. The Equals method and GetHashCode method on T /// will be used to compare items for equality. /// /// /// Items that are null are permitted, and will be sorted before all other items. /// public Set(): this(EqualityComparer.Default) { } /// /// Creates a new Set. The Equals and GetHashCode method of the passed comparer object /// will be used to compare items in this set. /// /// An instance of IEqualityComparer<T> that will be used to compare items. public Set(IEqualityComparer equalityComparer) { if (equalityComparer == null) throw new ArgumentNullException("equalityComparer"); this.equalityComparer = equalityComparer; hash = new Hash(equalityComparer); } /// /// Creates a new Set. The Equals method and GetHashCode method on T /// will be used to compare items for equality. /// /// /// Items that are null are permitted. /// /// A collection with items to be placed into the Set. public Set(IEnumerable collection): this(collection, EqualityComparer.Default) { } /// /// Creates a new Set. The Equals and GetHashCode method of the passed comparer object /// 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 Set. /// An instance of IEqualityComparer<T> that will be used to compare items. public Set(IEnumerable collection, IEqualityComparer equalityComparer) : this(equalityComparer) { AddMany(collection); } /// /// Creates a new Set given a comparer and a tree that contains the data. Used /// internally for Clone. /// /// EqualityComparer for the set. /// Data for the set. private Set(IEqualityComparer equalityComparer, Hash hash) { this.equalityComparer = equalityComparer; this.hash = hash; } #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 Set Clone() { Set newSet = new Set(equalityComparer, hash.Clone(null)); 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), where N is the number of items in the set. /// The cloned set. /// T is a reference type that does not implement ICloneable. public Set CloneContents() { bool itemIsValueType; if (!Util.IsCloneableType(typeof(T), out itemIsValueType)) throw new InvalidOperationException(string.Format(Strings.TypeNotCloneable, typeof(T).FullName)); Set clone = new Set(equalityComparer); // 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 IEqualityComparer<T> used to compare items in this set. /// /// If the set was created using a comparer, that comparer is returned. Otherwise /// the default comparer for T (EqualityComparer<T>.Default) is returned. public IEqualityComparer Comparer { get { return this.equalityComparer; } } /// /// 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 hash.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.

///

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

///
/// An enumerator for enumerating all the items in the Set. public sealed override IEnumerator GetEnumerator() { return hash.GetEnumerator(); } /// /// Determines if this set contains an item equal to . The set /// is not changed. /// /// Searching the set for an item takes approximately constant time, regardless of 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 hash.Find(item, 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 approximately constant time, regardless of the number of items in the set. /// /// In the following example, the set contains strings which are compared in a case-insensitive manner. /// /// Set<string> set = new Set<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 hash.Find(item, false, out foundItem); } #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 approximately constant time, regardless of 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 !hash.Insert(item, true, out dummy); } /// /// 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 approximately constant time, regardless of 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. 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), where 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 approximately constant time, regardless of the size of 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 hash.Delete(item, out dummy); } /// /// Removes all the items in from the set. /// /// /// Equality between items is determined by the comparison instance or delegate used /// to create the set. /// Removing the collection takes time O(M), where 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 set takes a constant amount of time, regardless of the number of items in it. public sealed override void Clear() { hash.StopEnumerations(); // Invalidate any enumerations. // The simplest and fastest way is simply to throw away the old tree and create a new one. hash = new Hash(equalityComparer); } #endregion Removing elements #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(Set otherSet) { if (otherSet == null) throw new ArgumentNullException("otherSet"); if (!object.Equals(equalityComparer, otherSet.equalityComparer)) 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), where M is the size of the /// . /// /// Set 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(Set 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 . /// /// IsProperSubsetOf is computed in time O(M), where M is the size of /// . /// Set 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(Set 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), where 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(Set 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 . /// /// IsProperSubsetOf is computed in time O(N), where 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(Set 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(Set otherSet) { CheckConsistentComparison(otherSet); // Must be the same size. if (otherSet.Count != this.Count) return false; // 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 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), where 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(Set otherSet) { CheckConsistentComparison(otherSet); Set 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. 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), 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(Set otherSet) { CheckConsistentComparison(otherSet); AddMany(otherSet); } /// /// 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), where M is the size of the /// one set, and N is the size of the other set. /// /// Set to union with. /// The union of the two sets. /// This set and don't use the same method for comparing items. public Set Union(Set otherSet) { CheckConsistentComparison(otherSet); Set 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), where 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(Set otherSet) { CheckConsistentComparison(otherSet); hash.StopEnumerations(); Set smaller, larger; if (otherSet.Count > this.Count) { smaller = this; larger = otherSet; } else { smaller = otherSet; larger = this; } T dummy; Hash newHash = new Hash(equalityComparer); foreach (T item in smaller) { if (larger.Contains(item)) newHash.Insert(item, true, out dummy); } hash = newHash; } /// /// 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), where 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 Set Intersection(Set otherSet) { CheckConsistentComparison(otherSet); Set smaller, larger, result; if (otherSet.Count > this.Count) { smaller = this; larger = otherSet; } else { smaller = otherSet; larger = this; } result = new Set(equalityComparer); 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(N), where 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(Set 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(N), where 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 Set Difference(Set otherSet) { CheckConsistentComparison(otherSet); Set 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(N), where 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(Set otherSet) { // main algorithm doesn't work correctly otherwise. if (this == otherSet) Clear(); CheckConsistentComparison(otherSet); if (otherSet.Count > this.Count) { hash.StopEnumerations(); Hash newHash = otherSet.hash.Clone(null); T dummy; foreach (T item in this) { if (newHash.Find(item, false, out dummy)) newHash.Delete(item, out dummy); else newHash.Insert(item, true, out dummy); } this.hash = newHash; } else { 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(N), where 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 Set SymmetricDifference(Set otherSet) { CheckConsistentComparison(otherSet); Set 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 } }