//****************************** // 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 { /// /// Bag<T> is a collection that contains items of type T. /// Unlike a Set, duplicate items (items that compare equal to each other) are allowed in an Bag. /// /// ///

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.

///

Bag 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 bag.

///

When multiple equal items are stored in the bag, they are stored as a representative item and a count. /// If equal items can be distinguished, this may be noticable. For example, if a case-insensitive /// comparer is used with a Bag<string>, and both "hello", and "HELLO" are added to the bag, then the /// bag will appear to contain two copies of "hello" (the representative item).

///

is similar, but uses comparison instead of hashing, maintain /// the items in sorted order, and stores distinct copies of items that compare equal.

///
/// [Serializable] public class Bag : CollectionBase, ICloneable { // The comparer used to compare KeyValuePairs. Equals and GetHashCode are used. private IEqualityComparer> equalityComparer; // The comparer used to compare items. Kept just for the Comparer property. private IEqualityComparer keyEqualityComparer; // The hash that actually does the work of storing the items. Each item is // stored as a representative item, and a count. private Hash> hash; // The total number of items stored in the bag. private int count; /// /// Helper function to create a new KeyValuePair struct with an item and a count. /// /// The item. /// The number of appearances. /// A new KeyValuePair. private static KeyValuePair NewPair(T item, int count) { KeyValuePair pair = new KeyValuePair(item, count); return pair; } /// /// Helper function to create a new KeyValuePair struct with a count of zero. /// /// The item. /// A new KeyValuePair. private static KeyValuePair NewPair(T item) { KeyValuePair pair = new KeyValuePair(item, 0); return pair; } #region Constructors /// /// Creates a new Bag. /// /// /// Items that are null are permitted. /// public Bag(): this(EqualityComparer.Default) { } /// /// Creates a new Bag. The Equals and GetHashCode methods of the passed comparison object /// will be used to compare items in this bag for equality. /// /// An instance of IEqualityComparer<T> that will be used to compare items. public Bag(IEqualityComparer equalityComparer) { if (equalityComparer == null) throw new ArgumentNullException("equalityComparer"); this.keyEqualityComparer = equalityComparer; this.equalityComparer = Comparers.EqualityComparerKeyValueFromComparerKey(equalityComparer); this.hash = new Hash>(this.equalityComparer); } /// /// Creates a new Bag. The bag is /// initialized with all the items in the given collection. /// /// /// Items that are null are permitted. /// /// A collection with items to be placed into the Bag. public Bag(IEnumerable collection): this(collection, EqualityComparer.Default) { } /// /// Creates a new Bag. The Equals and GetHashCode methods of the passed comparison object /// will be used to compare items in this bag. The bag is /// initialized with all the items in the given collection. /// /// A collection with items to be placed into the Bag. /// An instance of IEqualityComparer<T> that will be used to compare items. public Bag(IEnumerable collection, IEqualityComparer equalityComparer) : this(equalityComparer) { AddMany(collection); } /// /// Creates a new Bag given a comparer and a hash that contains the data. Used /// internally for Clone. /// /// IEqualityComparer for the bag. /// IEqualityComparer for the key. /// Data for the bag. /// Size of the bag. private Bag(IEqualityComparer> equalityComparer, IEqualityComparer keyEqualityComparer, Hash> hash, int count) { this.equalityComparer = equalityComparer; this.keyEqualityComparer = keyEqualityComparer; this.hash = hash; this.count = count; } #endregion Constructors #region Cloning /// /// Makes a shallow clone of this bag; i.e., if items of the /// bag 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 bag takes time O(N), where N is the number of items in the bag. /// The cloned bag. object ICloneable.Clone() { return this.Clone(); } /// /// Makes a shallow clone of this bag; i.e., if items of the /// bag 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 bag takes time O(N), where N is the number of unquie items in the bag. /// The cloned bag. public Bag Clone() { Bag newBag = new Bag(equalityComparer, keyEqualityComparer, hash.Clone(null), count); return newBag; } /// /// Makes a deep clone of this bag. A new bag is created with a clone of /// each element of this bag, 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 bag takes time O(N log N), where N is the number of items in the bag. /// The cloned bag. /// T is a reference type that does not implement ICloneable. public Bag CloneContents() { bool itemIsValueType; if (!Util.IsCloneableType(typeof(T), out itemIsValueType)) throw new InvalidOperationException(string.Format(Strings.TypeNotCloneable, typeof(T).FullName)); Hash> newHash = new Hash>(equalityComparer); // Clone each item, and add it to the new ordered bag. foreach (KeyValuePair pair in hash) { KeyValuePair newPair, dummy; T newKey; if (!itemIsValueType && pair.Key != null) newKey = (T)(((ICloneable)pair.Key).Clone()); else newKey = pair.Key; newPair = NewPair(newKey, pair.Value); newHash.Insert(newPair, true, out dummy); } return new Bag(equalityComparer, keyEqualityComparer, newHash, count); } #endregion Cloning #region Basic collection containment /// /// Returns the IEqualityComparer<T> used to compare items in this bag. /// /// If the bag 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 keyEqualityComparer; } } /// /// Returns the number of items in the bag. /// /// The size of the bag is returned in constant time. /// The number of items in the bag. public sealed override int Count { get { return count; } } /// /// Returns the number of copies of in the bag. /// /// NumberOfCopies() takes approximately constant time, no matter how many items /// are stored in the bag. /// The item to search for in the bag. /// The number of items in the bag that compare equal to . public int NumberOfCopies(T item) { KeyValuePair foundPair; if (hash.Find(NewPair(item), false, out foundPair)) return foundPair.Value; else return 0; } /// /// Returns the representative item stored in the bag that is equal to /// the provided item. Also returns the number of copies of the item in the bag. /// /// Item to find in the bag. /// If one or more items equal to are present in the /// bag, returns the representative item. If no items equal to are stored in the bag, /// returns . /// The number of items equal to stored in the bag. public int GetRepresentativeItem(T item, out T representative) { KeyValuePair foundPair; if (hash.Find(NewPair(item), false, out foundPair)) { representative = foundPair.Key; return foundPair.Value; } else { representative = item; return 0; } } /// /// Returns an enumerator that enumerates all the items in the bag. /// If an item is present multiple times in the bag, the representative item is yielded by the /// enumerator multiple times. The order of enumeration is haphazard and may change. /// /// ///

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 bag while it is being enumerated, then /// the enumeration will end with an InvalidOperationException.

///

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

///
/// An enumerator for enumerating all the items in the Bag. public sealed override IEnumerator GetEnumerator() { foreach (KeyValuePair pair in hash) { for (int i = 0; i < pair.Value; ++i) yield return pair.Key; } } /// /// Determines if this bag contains an item equal to . The bag /// is not changed. /// /// Searching the bag for an item takes time O(log N), where N is the number of items in the bag. /// The item to search for. /// True if the bag contains . False if the bag does not contain . public sealed override bool Contains(T item) { KeyValuePair dummy; return hash.Find(NewPair(item), false, out dummy); } /// /// Enumerates all the items in the bag, but enumerates equal items /// just once, even if they occur multiple times in the bag. /// /// If the bag is changed while items are being enumerated, the /// enumeration will terminate with an InvalidOperationException. /// An IEnumerable<T> that enumerates the unique items. public IEnumerable DistinctItems() { foreach (KeyValuePair pair in hash) { yield return pair.Key; } } #endregion #region Adding elements /// /// Adds a new item to the bag. Since bags can contain duplicate items, the item /// is added even if the bag already contains an item equal to . In /// this case, the count of items for the representative item is increased by one, but the existing /// represetative item is unchanged. /// /// /// Adding an item takes approximately constant time, regardless of the number of items in the bag. /// The item to add to the bag. public sealed override void Add(T item) { KeyValuePair pair = NewPair(item, 1); KeyValuePair existing, newPair; if (! hash.Insert(pair, false, out existing)) { // The item already existed, so update the count instead. newPair = NewPair(existing.Key, existing.Value + 1); hash.Insert(newPair, true, out pair); } ++count; } // CONSIDER: add an example to the documentation below. /// /// Adds a new item to the bag. Since bags can contain duplicate items, the item /// is added even if the bag already contains an item equal to . In /// this case (unlike Add), the new item becomes the representative item. /// /// /// Adding an item takes approximately constant time, regardless of the number of items in the bag. /// The item to add to the bag. public void AddRepresentative(T item) { KeyValuePair pair = NewPair(item, 1); KeyValuePair existing, newPair; if (!hash.Insert(pair, false, out existing)) { // The item already existed, so update the count instead. newPair = NewPair(pair.Key, existing.Value + 1); hash.Insert(newPair, true, out pair); } ++count; } /// /// Changes the number of copies of an existing item in the bag, or adds the indicated number /// of copies of the item to the bag. /// /// /// Changing the number of copies takes approximately constant time, regardless of the number of items in the bag. /// The item to change the number of copies of. This may or may not already be present in the bag. /// The new number of copies of the item. public void ChangeNumberOfCopies(T item, int numCopies) { if (numCopies == 0) RemoveAllCopies(item); else { KeyValuePair dummy, existing, newPair; if (hash.Find(NewPair(item), false, out existing)) { count += numCopies - existing.Value; newPair = NewPair(existing.Key, numCopies); } else { count += numCopies; newPair = NewPair(item, numCopies); } hash.Insert(newPair, true, out dummy); } } /// /// Adds all the items in to the bag. /// /// /// Adding the collection takes time O(M log N), where N is the number of items in the bag, and M is the /// number of items in . /// A collection of items to add to the bag. public void AddMany(IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); // If we're adding ourselves, we need to copy to a separate array to avoid modification // during enumeration. if (this == collection) collection = this.ToArray(); foreach (T item in collection) Add(item); } #endregion Adding elements #region Removing elements /// /// Searches the bag for one item equal to , and if found, /// removes it from the bag. If not found, the bag is unchanged. /// /// /// Equality between items is determined by the comparison instance or delegate used /// to create the bag. /// Removing an item from the bag takes approximated constant time, /// regardless of the number of items in the bag. /// The item to remove. /// True if was found and removed. False if was not in the bag. public sealed override bool Remove(T item) { KeyValuePair removed, newPair; if (hash.Delete(NewPair(item), out removed)) { if (removed.Value > 1) { // Only want to remove one copied, so add back in with a reduced count. KeyValuePair dummy; newPair = NewPair(removed.Key, removed.Value - 1); hash.Insert(newPair, true, out dummy); } --count; return true; } else return false; } /// /// Searches the bag for all items equal to , and /// removes all of them from the bag. If not found, the bag is unchanged. /// /// /// Equality between items is determined by the comparer instance used /// to create the bag. /// RemoveAllCopies() takes time O(M log N), where N is the total number of items in the bag, and M is /// the number of items equal to . /// The item to remove. /// The number of copies of that were found and removed. public int RemoveAllCopies(T item) { KeyValuePair removed; if (hash.Delete(NewPair(item), out removed)) { count -= removed.Value; return removed.Value; } else return 0; } /// /// Removes all the items in from the bag. Items that /// are not present in the bag are ignored. /// /// /// Equality between items is determined by the comparer instance used /// to create the bag. /// Removing the collection takes time O(M), where M is the /// number of items in . /// A collection of items to remove from the bag. /// The number of items removed from the bag. /// 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 bag. /// /// Clearing the bag 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 hash and create a new one. hash = new Hash>(equalityComparer); count = 0; } #endregion Removing elements #region Set operations /// /// Check that this bag and another bag were created with the same comparison /// mechanism. Throws exception if not compatible. /// /// Other bag to check comparision mechanism. /// If otherBag and this bag don't use the same method for comparing items. private void CheckConsistentComparison(Bag otherBag) { if (otherBag == null) throw new ArgumentNullException("otherBag"); if (!object.Equals(equalityComparer, otherBag.equalityComparer)) throw new InvalidOperationException(Strings.InconsistentComparisons); } /// /// Determines if this bag is equal to another bag. This bag is equal to /// if they contain the same number of /// of copies of equal elements. /// /// IsSupersetOf is computed in time O(N), where N is the number of unique items in /// this bag. /// Bag to compare to /// True if this bag is equal to , false otherwise. /// This bag and don't use the same method for comparing items. public bool IsEqualTo(Bag otherBag) { CheckConsistentComparison(otherBag); // Must be the same size. if (otherBag.Count != this.Count) return false; // Check each item to make sure it is in this set the same number of times. foreach (T item in otherBag.DistinctItems()) { if (this.NumberOfCopies(item) != otherBag.NumberOfCopies(item)) return false; } return true; } /// /// Determines if this bag is a superset of another bag. Neither bag is modified. /// This bag is a superset of if every element in /// is also in this bag, at least the same number of /// times. /// /// IsSupersetOf is computed in time O(M), where M is the number of unique items in /// . /// Bag to compare to. /// True if this is a superset of . /// This bag and don't use the same method for comparing items. public bool IsSupersetOf(Bag otherBag) { CheckConsistentComparison(otherBag); if (otherBag.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 otherBag.DistinctItems()) { if (this.NumberOfCopies(item) < otherBag.NumberOfCopies(item)) return false; } return true; } /// /// Determines if this bag is a proper superset of another bag. Neither bag is modified. /// This bag is a proper superset of if every element in /// is also in this bag, at least the same number of /// times. Additional, this bag must have strictly more items than . /// /// IsProperSupersetOf is computed in time O(M), where M is the number of unique items in /// . /// Set to compare to. /// True if this is a proper superset of . /// This bag and don't use the same method for comparing items. public bool IsProperSupersetOf(Bag otherBag) { CheckConsistentComparison(otherBag); if (otherBag.Count >= this.Count) return false; // Can't be a proper superset of a bigger or equal set return IsSupersetOf(otherBag); } /// /// Determines if this bag is a subset of another ba11 items in this bag. /// /// Bag to compare to. /// True if this is a subset of . /// This bag and don't use the same method for comparing items. public bool IsSubsetOf(Bag otherBag) { return otherBag.IsSupersetOf(this); } /// /// Determines if this bag is a proper subset of another bag. Neither bag is modified. /// This bag is a subset of if every element in this bag /// is also in , at least the same number of /// times. Additional, this bag must have strictly fewer items than . /// /// IsProperSubsetOf is computed in time O(N), where N is the number of unique items in this bag. /// Bag to compare to. /// True if this is a proper subset of . /// This bag and don't use the same method for comparing items. public bool IsProperSubsetOf(Bag otherBag) { return otherBag.IsProperSupersetOf(this); } /// /// Determines if this bag is disjoint from another bag. Two bags are disjoint /// if no item from one set is equal to any item in the other bag. /// /// /// The answer is computed in time O(N), where N is the size of the smaller set. /// /// Bag to check disjointness with. /// True if the two bags are disjoint, false otherwise. /// This bag and don't use the same method for comparing items. public bool IsDisjointFrom(Bag otherBag) { CheckConsistentComparison(otherBag); Bag smaller, larger; if (otherBag.Count > this.Count) { smaller = this; larger = otherBag; } else { smaller = otherBag; larger = this; } foreach (T item in smaller.DistinctItems()) { if (larger.Contains(item)) return false; } return true; } /// /// Computes the union of this bag with another bag. The union of two bags /// is all items from both of the bags. If an item appears X times in one bag, /// and Y times in the other bag, the union contains the item Maximum(X,Y) times. This bag receives /// the union of the two bags, the other bag is unchanged. /// /// /// The union of two bags is computed in time O(M+N), where M and N are the size of the /// two bags. /// /// Bag to union with. /// This bag and don't use the same method for comparing items. public void UnionWith(Bag otherBag) { CheckConsistentComparison(otherBag); if (otherBag == this) return; // Nothing to do int copiesInThis, copiesInOther; // Enumerate each of the items in the other bag. Add items that need to be // added to this bag. foreach (T item in otherBag.DistinctItems()) { copiesInThis = this.NumberOfCopies(item); copiesInOther = otherBag.NumberOfCopies(item); if (copiesInOther > copiesInThis) ChangeNumberOfCopies(item, copiesInOther); } } /// /// Computes the union of this bag with another bag. The union of two bags /// is all items from both of the bags. If an item appears X times in one bag, /// and Y times in the other bag, the union contains the item Maximum(X,Y) times. A new bag is /// created with the union of the bags and is returned. This bag and the other bag /// are unchanged. /// /// /// The union of two bags is computed in time O(M+N), where M and N are the size of the two bags. /// /// Bag to union with. /// The union of the two bags. /// This bag and don't use the same method for comparing items. public Bag Union(Bag otherBag) { CheckConsistentComparison(otherBag); Bag smaller, larger, result; if (otherBag.Count > this.Count) { smaller = this; larger = otherBag; } else { smaller = otherBag; larger = this; } result = larger.Clone(); result.UnionWith(smaller); return result; } /// /// Computes the sum of this bag with another bag. The sum of two bags /// is all items from both of the bags. If an item appears X times in one bag, /// and Y times in the other bag, the sum contains the item (X+Y) times. This bag receives /// the sum of the two bags, the other bag is unchanged. /// /// /// The sum of two bags is computed in time O(M), where M is the size of the /// other bag.. /// /// Bag to sum with. /// This bag and don't use the same method for comparing items. public void SumWith(Bag otherBag) { CheckConsistentComparison(otherBag); if (this == otherBag) { // Not very efficient, but an uncommon case. AddMany(otherBag); return; } int copiesInThis, copiesInOther; // Enumerate each of the items in the other bag. Add items that need to be // added to this bag. foreach (T item in otherBag.DistinctItems()) { copiesInThis = this.NumberOfCopies(item); copiesInOther = otherBag.NumberOfCopies(item); ChangeNumberOfCopies(item, copiesInThis + copiesInOther); } } /// /// Computes the sum of this bag with another bag. he sum of two bags /// is all items from both of the bags. If an item appears X times in one bag, /// and Y times in the other bag, the sum contains the item (X+Y) times. A new bag is /// created with the sum of the bags and is returned. This bag and the other bag /// are unchanged. /// /// /// The sum of two bags is computed in time O(M + N log M), where M is the size of the /// larger bag, and N is the size of the smaller bag. /// /// Bag to sum with. /// The sum of the two bags. /// This bag and don't use the same method for comparing items. public Bag Sum(Bag otherBag) { CheckConsistentComparison(otherBag); Bag smaller, larger, result; if (otherBag.Count > this.Count) { smaller = this; larger = otherBag; } else { smaller = otherBag; larger = this; } result = larger.Clone(); result.SumWith(smaller); return result; } /// /// Computes the intersection of this bag with another bag. The intersection of two bags /// is all items that appear in both of the bags. If an item appears X times in one bag, /// and Y times in the other bag, the sum contains the item Minimum(X,Y) times. This bag receives /// the intersection of the two bags, the other bag is unchanged. /// /// /// When equal items appear in both bags, the intersection will include an arbitrary choice of one of the /// two equal items. /// The intersection of two bags is computed in time O(N), where N is the size of the smaller bag. /// /// Bag to intersection with. /// This bag and don't use the same method for comparing items. public void IntersectionWith(Bag otherBag) { CheckConsistentComparison(otherBag); hash.StopEnumerations(); Bag smaller, larger; if (otherBag.Count > this.Count) { smaller = this; larger = otherBag; } else { smaller = otherBag; larger = this; } KeyValuePair dummy; Hash> newHash = new Hash>(equalityComparer); int newCount = 0; int copiesInSmaller, copiesInLarger, copies; // Enumerate each of the items in the smaller bag. Add items that need to be // added to the intersection. foreach (T item in smaller.DistinctItems()) { copiesInLarger = larger.NumberOfCopies(item); copiesInSmaller = smaller.NumberOfCopies(item); copies = Math.Min(copiesInLarger, copiesInSmaller); if (copies > 0) { newHash.Insert(NewPair(item, copies), true, out dummy); newCount += copies; } } hash = newHash; count = newCount; } /// /// Computes the intersection of this bag with another bag. The intersection of two bags /// is all items that appear in both of the bags. If an item appears X times in one bag, /// and Y times in the other bag, the intersection contains the item Minimum(X,Y) times. A new bag is /// created with the intersection of the bags and is returned. This bag and the other bag /// are unchanged. /// /// /// When equal items appear in both bags, the intersection will include an arbitrary choice of one of the /// two equal items. /// The intersection of two bags is computed in time O(N), where N is the size of the smaller bag. /// /// Bag to intersection with. /// The intersection of the two bags. /// This bag and don't use the same method for comparing items. public Bag Intersection(Bag otherBag) { CheckConsistentComparison(otherBag); Bag smaller, larger, result; if (otherBag.Count > this.Count) { smaller = this; larger = otherBag; } else { smaller = otherBag; larger = this; } int copiesInSmaller, copiesInLarger, copies; // Enumerate each of the items in the smaller bag. Add items that need to be // added to the intersection. result = new Bag(keyEqualityComparer); foreach (T item in smaller.DistinctItems()) { copiesInLarger = larger.NumberOfCopies(item); copiesInSmaller = smaller.NumberOfCopies(item); copies = Math.Min(copiesInLarger, copiesInSmaller); if (copies > 0) result.ChangeNumberOfCopies(item, copies); } return result; } /// /// Computes the difference of this bag with another bag. The difference of these two bags /// is all items that appear in this bag, but not in . If an item appears X times in this bag, /// and Y times in the other bag, the difference contains the item X - Y times (zero times if Y >= X). This bag receives /// the difference of the two bags; the other bag is unchanged. /// /// /// The difference of two bags is computed in time O(M), where M is the size of the /// other bag. /// /// Bag to difference with. /// This bag and don't use the same method for comparing items. public void DifferenceWith(Bag otherBag) { CheckConsistentComparison(otherBag); if (this == otherBag) { Clear(); return; } int copiesInThis, copiesInOther, copies; // Enumerate each of the items in the other bag. Remove items that need to be // removed from this bag. foreach (T item in otherBag.DistinctItems()) { copiesInThis = this.NumberOfCopies(item); copiesInOther = otherBag.NumberOfCopies(item); copies = copiesInThis - copiesInOther; if (copies < 0) copies = 0; ChangeNumberOfCopies(item, copies); } } /// /// Computes the difference of this bag with another bag. The difference of these two bags /// is all items that appear in this bag, but not in . If an item appears X times in this bag, /// and Y times in the other bag, the difference contains the item X - Y times (zero times if Y >= X). A new bag is /// created with the difference of the bags and is returned. This bag and the other bag /// are unchanged. /// /// /// The difference of two bags is computed in time O(M + N), where M and N are the size /// of the two bags. /// /// Bag to difference with. /// The difference of the two bags. /// This bag and don't use the same method for comparing items. public Bag Difference(Bag otherBag) { Bag result; CheckConsistentComparison(otherBag); result = this.Clone(); result.DifferenceWith(otherBag); return result; } /// /// Computes the symmetric difference of this bag with another bag. The symmetric difference of two bags /// is all items that appear in either of the bags, but not both. If an item appears X times in one bag, /// and Y times in the other bag, the symmetric difference contains the item AbsoluteValue(X - Y) times. This bag receives /// the symmetric difference of the two bags; the other bag is unchanged. /// /// /// The symmetric difference of two bags is computed in time O(M + N), where M is the size of the /// larger bag, and N is the size of the smaller bag. /// /// Bag to symmetric difference with. /// This bag and don't use the same method for comparing items. public void SymmetricDifferenceWith(Bag otherBag) { CheckConsistentComparison(otherBag); if (this == otherBag) { Clear(); return; } int copiesInThis, copiesInOther, copies; // Enumerate each of the items in the other bag. Add items that need to be // added to this bag. foreach (T item in otherBag.DistinctItems()) { copiesInThis = this.NumberOfCopies(item); copiesInOther = otherBag.NumberOfCopies(item); copies = Math.Abs(copiesInThis - copiesInOther); if (copies != copiesInThis) ChangeNumberOfCopies(item, copies); } } /// /// Computes the symmetric difference of this bag with another bag. The symmetric difference of two bags /// is all items that appear in either of the bags, but not both. If an item appears X times in one bag, /// and Y times in the other bag, the symmetric difference contains the item AbsoluteValue(X - Y) times. A new bag is /// created with the symmetric difference of the bags and is returned. This bag and the other bag /// are unchanged. /// /// /// The symmetric difference of two bags is computed in time O(M + N), where M is the size of the /// larger bag, and N is the size of the smaller bag. /// /// Bag to symmetric difference with. /// The symmetric difference of the two bags. /// This bag and don't use the same method for comparing items. public Bag SymmetricDifference(Bag otherBag) { CheckConsistentComparison(otherBag); Bag smaller, larger, result; if (otherBag.Count > this.Count) { smaller = this; larger = otherBag; } else { smaller = otherBag; larger = this; } result = larger.Clone(); result.SymmetricDifferenceWith(smaller); return result; } #endregion Set operations } }