internalstruct Slot { internalint hashCode; // Lower 31 bits of hash code, -1 if unused internalint next; // Index of next entry, -1 if last internal T value; }
privatevoidIncreaseCapacity() { Debug.Assert(m_buckets != null, "IncreaseCapacity called on a set with no elements"); int newSize = HashHelpers.ExpandPrime(m_count); if (newSize <= m_count) { thrownew ArgumentException(SR.GetString(SR.Arg_HSCapacityOverflow)); } // Able to increase capacity; copy elements to larger array and rehash SetCapacity(newSize, false); } ///<summary> /// Set the underlying buckets array to size newSize and rehash. Note that newSize /// *must* be a prime. It is very likely that you want to call IncreaseCapacity() /// instead of this method. ///</summary> privatevoidSetCapacity(int newSize, bool forceNewHashCodes) { Contract.Assert(HashHelpers.IsPrime(newSize), "New size is not prime!"); Contract.Assert(m_buckets != null, "SetCapacity called on a set with no elements"); Slot[] newSlots = new Slot[newSize]; if (m_slots != null) { Array.Copy(m_slots, 0, newSlots, 0, m_lastIndex); } if(forceNewHashCodes) { for(int i = 0; i < m_lastIndex; i++) { if(newSlots[i].hashCode != -1) { newSlots[i].hashCode = InternalGetHashCode(newSlots[i].value); } } } int[] newBuckets = newint[newSize]; for (int i = 0; i < m_lastIndex; i++) { int bucket = newSlots[i].hashCode % newSize; newSlots[i].next = newBuckets[bucket] - 1; newBuckets[bucket] = i + 1; } m_slots = newSlots; m_buckets = newBuckets; }
publicvoidIntersectWith(IEnumerable<T> other) { if (other == null) { thrownew ArgumentNullException("other"); } Contract.EndContractBlock(); // intersection of anything with empty set is empty set, so return if count is 0 if (m_count == 0) { return; } // if other is empty, intersection is empty set; remove all elements and we're done // can only figure this out if implements ICollection<T>. (IEnumerable<T> has no count) ICollection<T> otherAsCollection = other as ICollection<T>; if (otherAsCollection != null) { if (otherAsCollection.Count == 0) { Clear(); return; } HashSet<T> otherAsSet = other as HashSet<T>; // faster if other is a hashset using same equality comparer; so check // that other is a hashset using the same equality comparer. if (otherAsSet != null && AreEqualityComparersEqual(this, otherAsSet)) { IntersectWithHashSetWithSameEC(otherAsSet); return; } } IntersectWithEnumerable(other); }
///<summary> /// If other is a hashset that uses same equality comparer, intersect is much faster /// because we can use other's Contains ///</summary> ///<param name="other"></param> privatevoidIntersectWithHashSetWithSameEC(HashSet<T> other) { for (int i = 0; i < m_lastIndex; i++) { if (m_slots[i].hashCode >= 0) { T item = m_slots[i].value; if (!other.Contains(item)) { Remove(item); } } } }
///<summary> /// Iterate over other. If contained in this, mark an element in bit array corresponding to /// its position in m_slots. If anything is unmarked (in bit array), remove it. /// /// This attempts to allocate on the stack, if below StackAllocThreshold. ///</summary> ///<param name="other"></param> [System.Security.SecuritySafeCritical] privateunsafevoidIntersectWithEnumerable(IEnumerable<T> other) { Debug.Assert(m_buckets != null, "m_buckets shouldn't be null; callers should check first"); // keep track of current last index; don't want to move past the end of our bit array // (could happen if another thread is modifying the collection) int originalLastIndex = m_lastIndex; int intArrayLength = BitHelper.ToIntArrayLength(originalLastIndex); BitHelper bitHelper; if (intArrayLength <= StackAllocThreshold) { int* bitArrayPtr = stackallocint[intArrayLength]; bitHelper = new BitHelper(bitArrayPtr, intArrayLength); } else { int[] bitArray = newint[intArrayLength]; bitHelper = new BitHelper(bitArray, intArrayLength); } // mark if contains: find index of in slots array and mark corresponding element in bit array foreach (T item in other) { int index = InternalIndexOf(item); if (index >= 0) { bitHelper.MarkBit(index); } } // if anything unmarked, remove it. Perf can be optimized here if BitHelper had a // FindFirstUnmarked method. for (int i = 0; i < originalLastIndex; i++) { if (m_slots[i].hashCode >= 0 && !bitHelper.IsMarked(i)) { Remove(m_slots[i].value); } } }
///<summary> /// Used internally by set operations which have to rely on bit array marking. This is like /// Contains but returns index in slots array. ///</summary> ///<param name="item"></param> ///<returns></returns> privateintInternalIndexOf(T item) { Debug.Assert(m_buckets != null, "m_buckets was null; callers should check first"); int hashCode = InternalGetHashCode(item); for (int i = m_buckets[hashCode % m_buckets.Length] - 1; i >= 0; i = m_slots[i].next) { if ((m_slots[i].hashCode) == hashCode && m_comparer.Equals(m_slots[i].value, item)) { return i; } } // wasn't found return-1; }
publicvoidExceptWith(IEnumerable<T> other) { if (other == null) { thrownew ArgumentNullException("other"); } Contract.EndContractBlock(); // this is already the enpty set; return if (m_count == 0) { return; } // special case if other is this; a set minus itself is the empty set if (other == this) { Clear(); return; } // remove every element in other from this foreach (T element in other) { Remove(element); } }
publicvoidSymmetricExceptWith(IEnumerable<T> other) { if (other == null) { thrownew ArgumentNullException("other"); } Contract.EndContractBlock(); // if set is empty, then symmetric difference is other if (m_count == 0) { UnionWith(other); return; } // special case this; the symmetric difference of a set with itself is the empty set if (other == this) { Clear(); return; } HashSet<T> otherAsSet = other as HashSet<T>; // If other is a HashSet, it has unique elements according to its equality comparer, // but if they're using different equality comparers, then assumption of uniqueness // will fail. So first check if other is a hashset using the same equality comparer; // symmetric except is a lot faster and avoids bit array allocations if we can assume // uniqueness if (otherAsSet != null && AreEqualityComparersEqual(this, otherAsSet)) { SymmetricExceptWithUniqueHashSet(otherAsSet); } else { SymmetricExceptWithEnumerable(other); } }
publicboolIsSubsetOf(IEnumerable<T> other) { if (other == null) { thrownew ArgumentNullException("other"); } Contract.EndContractBlock(); // The empty set is a subset of any set if (m_count == 0) { returntrue; } HashSet<T> otherAsSet = other as HashSet<T>; // faster if other has unique elements according to this equality comparer; so check // that other is a hashset using the same equality comparer. if (otherAsSet != null && AreEqualityComparersEqual(this, otherAsSet)) { // if this has more elements then it can't be a subset if (m_count > otherAsSet.Count) { returnfalse; } // already checked that we're using same equality comparer. simply check that // each element in this is contained in other. return IsSubsetOfHashSetWithSameEC(otherAsSet); } else { ElementCount result = CheckUniqueAndUnfoundElements(other, false); return (result.uniqueCount == m_count && result.unfoundCount >= 0); } }
[System.Security.SecuritySafeCritical] privateunsafe ElementCount CheckUniqueAndUnfoundElements(IEnumerable<T> other, bool returnIfUnfound) { ElementCount result; // need special case in case this has no elements. if (m_count == 0) { int numElementsInOther = 0; foreach (T item in other) { numElementsInOther++; // break right away, all we want to know is whether other has 0 or 1 elements break; } result.uniqueCount = 0; result.unfoundCount = numElementsInOther; return result; } Debug.Assert((m_buckets != null) && (m_count > 0), "m_buckets was null but count greater than 0"); int originalLastIndex = m_lastIndex; int intArrayLength = BitHelper.ToIntArrayLength(originalLastIndex); BitHelper bitHelper; if (intArrayLength <= StackAllocThreshold) { int* bitArrayPtr = stackallocint[intArrayLength]; bitHelper = new BitHelper(bitArrayPtr, intArrayLength); } else { int[] bitArray = newint[intArrayLength]; bitHelper = new BitHelper(bitArray, intArrayLength); } // count of items in other not found in this int unfoundCount = 0; // count of unique items in other found in this int uniqueFoundCount = 0; foreach (T item in other) { int index = InternalIndexOf(item); if (index >= 0) { if (!bitHelper.IsMarked(index)) { // item hasn't been seen yet bitHelper.MarkBit(index); uniqueFoundCount++; } } else { unfoundCount++; if (returnIfUnfound) { break; } } } result.uniqueCount = uniqueFoundCount; result.unfoundCount = unfoundCount; return result; }
publicboolIsProperSubsetOf(IEnumerable<T> other) { if (other == null) { thrownew ArgumentNullException("other"); } Contract.EndContractBlock(); ICollection<T> otherAsCollection = other as ICollection<T>; if (otherAsCollection != null) { // the empty set is a proper subset of anything but the empty set if (m_count == 0) { return otherAsCollection.Count > 0; } HashSet<T> otherAsSet = other as HashSet<T>; // faster if other is a hashset (and we're using same equality comparer) if (otherAsSet != null && AreEqualityComparersEqual(this, otherAsSet)) { if (m_count >= otherAsSet.Count) { returnfalse; } // this has strictly less than number of items in other, so the following // check suffices for proper subset. return IsSubsetOfHashSetWithSameEC(otherAsSet); } } ElementCount result = CheckUniqueAndUnfoundElements(other, false); return (result.uniqueCount == m_count && result.unfoundCount > 0); }
publicboolIsSupersetOf(IEnumerable<T> other) { if (other == null) { thrownew ArgumentNullException("other"); } Contract.EndContractBlock(); // try to fall out early based on counts ICollection<T> otherAsCollection = other as ICollection<T>; if (otherAsCollection != null) { // if other is the empty set then this is a superset if (otherAsCollection.Count == 0) { returntrue; } HashSet<T> otherAsSet = other as HashSet<T>; // try to compare based on counts alone if other is a hashset with // same equality comparer if (otherAsSet != null && AreEqualityComparersEqual(this, otherAsSet)) { if (otherAsSet.Count > m_count) { returnfalse; } } } return ContainsAllElements(other); }
publicboolIsProperSupersetOf(IEnumerable<T> other) { if (other == null) { thrownew ArgumentNullException("other"); } Contract.EndContractBlock(); // the empty set isn't a proper superset of any set. if (m_count == 0) { returnfalse; } ICollection<T> otherAsCollection = other as ICollection<T>; if (otherAsCollection != null) { // if other is the empty set then this is a superset if (otherAsCollection.Count == 0) { // note that this has at least one element, based on above check returntrue; } HashSet<T> otherAsSet = other as HashSet<T>; // faster if other is a hashset with the same equality comparer if (otherAsSet != null && AreEqualityComparersEqual(this, otherAsSet)) { if (otherAsSet.Count >= m_count) { returnfalse; } // now perform element check return ContainsAllElements(otherAsSet); } } // couldn't fall out in the above cases; do it the long way ElementCount result = CheckUniqueAndUnfoundElements(other, true); return (result.uniqueCount < m_count && result.unfoundCount == 0); }
集合重叠
判断两个集合是否重叠,与求交集逻辑类似,不同的是两个集合只要发现有一个共有的元素就可以结束遍历了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
publicboolOverlaps(IEnumerable<T> other) { if (other == null) { thrownew ArgumentNullException("other"); } Contract.EndContractBlock(); if (m_count == 0) { returnfalse; } foreach (T element in other) { if (Contains(element)) { returntrue; } } returnfalse; }