1
0
Fork 0
mirror of https://github.com/beefytech/Beef.git synced 2025-06-20 08:58:00 +02:00
Beef/BeefLibs/corlib/src/Collections/Generic/HashSet.bf
2019-12-13 14:25:15 -08:00

1166 lines
33 KiB
Beef

// This file contains portions of code released by Microsoft under the MIT license as part
// of an open-sourcing initiative in 2014 of the C# core libraries.
// The original source was submitted to https://github.com/Microsoft/referencesource
#if PARANOID
#define VERSION_HASHSET
#endif
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Text;
using System.Security;
namespace System.Collections.Generic
{
/// Implementation notes:
/// This uses an array-based implementation similar to Dictionary<T>, using a buckets array
/// to map hash values to the Slots array. Items in the Slots array that hash to the same value
/// are chained together through the "next" indices.
///
/// The capacity is always prime; so during resizing, the capacity is chosen as the next prime
/// greater than double the last capacity.
///
/// The underlying data structures are lazily initialized. Because of the observation that,
/// in practice, hashtables tend to contain only a few elements, the initial capacity is
/// set very small (3 elements) unless the ctor with a collection is used.
///
/// The +/- 1 modifications in methods that add, check for containment, etc allow us to
/// distinguish a hash code of 0 from an uninitialized bucket. This saves us from having to
/// reset each bucket to -1 when resizing. See Contains, for example.
///
/// Set methods such as UnionWith, IntersectWith, ExceptWith, and SymmetricExceptWith modify
/// this set.
///
/// Some operations can perform faster if we can assume "other" contains unique elements
/// according to this equality comparer. The only times this is efficient to check is if
/// other is a hashset. Note that checking that it's a hashset alone doesn't suffice; we
/// also have to check that the hashset is using the same equality comparer. If other
/// has a different equality comparer, it will have unique elements according to its own
/// equality comparer, but not necessarily according to ours. Therefore, to go these
/// optimized routes we check that other is a hashset using the same equality comparer.
///
/// A HashSet with no elements has the properties of the empty set. (See IsSubset, etc. for
/// special empty set checks.)
///
/// A couple of methods have a special case if other is this (e.g. SymmetricExceptWith).
/// If we didn't have these checks, we could be iterating over the set and modifying at
/// the same time.
public class HashSet<T> : IEnumerable<T> //: ICollection<T>, ISerializable, IDeserializationCallback, ISet<T>, IReadOnlyCollection<T>
where T : IHashable
{
// store lower 31 bits of hash code
private const int32 Lower31BitMask = 0x7FFFFFFF;
// cutoff point, above which we won't do stackallocs. This corresponds to 100 integers.
private const int32 StackAllocThreshold = 100;
// when constructing a hashset from an existing collection, it may contain duplicates,
// so this is used as the max acceptable excess ratio of capacity to count. Note that
// this is only used on the ctor and not to automatically shrink if the hashset has, e.g,
// a lot of adds followed by removes. Users must explicitly shrink by calling TrimExcess.
// This is set to 3 because capacity is acceptable as 2x rounded up to nearest prime.
private const int32 ShrinkThreshold = 3;
private int32[] mBuckets ~ delete _;
private Slot[] mSlots ~ delete _;
private int32 mCount;
private int32 mLastIndex;
private int32 mFreeList;
#if VERSION_HASHSET
private int32 mVersion;
const String cVersionError = "HashSet changed during enumeration";
#endif
public this()
{
mLastIndex = 0;
mCount = 0;
mFreeList = -1;
#if VERSION_HASHSET
mVersion = 0;
#endif
}
public this(int wantSize)
{
mLastIndex = 0;
mCount = 0;
mFreeList = -1;
#if VERSION_HASHSET
mVersion = 0;
#endif
Initialize((int32)wantSize);
}
/*public this()
: this(EqualityComparer<T>.Default) { }*/
/*public this(IEqualityComparer<T> comparer)
{
if (comparer == null)
{
comparer = EqualityComparer<T>.Default;
}
this.m_comparer = comparer;
m_lastIndex = 0;
m_count = 0;
m_freeList = -1;
m_version = 0;
}
public this(IEnumerable<T> collection)
: this(collection, EqualityComparer<T>.Default) { }
/// Implementation Notes:
/// Since resizes are relatively expensive (require rehashing), this attempts to minimize
/// the need to resize by setting the initial capacity based on size of collection.
public this(IEnumerable<T> collection, IEqualityComparer<T> comparer)
: this(comparer)
{
if (collection == null)
{
Runtime.FatalError();
}
Contract.EndContractBlock();
// to avoid excess resizes, first set size based on collection's count. Collection
// may contain duplicates, so call TrimExcess if resulting hashset is larger than
// threshold
int suggestedCapacity = 0;
ICollection<T> coll = collection as ICollection<T>;
if (coll != null)
{
suggestedCapacity = coll.Count;
}
Initialize(suggestedCapacity);
this.UnionWith(collection);
if ((m_count == 0 && m_slots.Length > HashHelpers.GetMinPrime()) ||
(m_count > 0 && m_slots.Length / m_count > ShrinkThreshold))
{
TrimExcess();
}
}*/
/// Add item to this hashset. This is the explicit implementation of the ICollection<T>
/// interface. The other Add method returns bool indicating whether item was added.
/// <param name="item">item to add</param>
/*void ICollection<T>.Add(T item)
{
AddIfNotPresent(item);
}
*/
/// Remove all items from this set. This clears the elements but not the underlying
/// buckets and slots array. Follow this call by TrimExcess to release these.
public void Clear()
{
if (mLastIndex > 0)
{
Debug.Assert(mBuckets != null, "m_buckets was null but m_lastIndex > 0");
// clear the elements so that the gc can reclaim the references.
// clear only up to m_lastIndex for m_slots
Array.Clear(mSlots, 0, mLastIndex);
Array.Clear(mBuckets, 0, mBuckets.Count);
mLastIndex = 0;
mCount = 0;
mFreeList = -1;
}
#if VERSION_HASHSET
mVersion++;
#endif
}
/// Checks if this hashset contains the item
/// @param item item to check for containment
/// @return true if item contained; false if not
public bool Contains(T item)
{
if (mBuckets != null)
{
int32 hashCode = (int32)InternalGetHashCode(item);
// see note at "HashSet" level describing why "- 1" appears in for loop
for (int32 i = mBuckets[hashCode % mBuckets.Count] - 1; i >= 0; i = mSlots[i].mNext)
{
if (mSlots[i].mHashCode == hashCode && /*m_comparer.Equals*/(mSlots[i].mValue == item))
{
return true;
}
}
}
// either m_buckets is null or wasn't found
return false;
}
public bool ContainsWith<TAltKey>(TAltKey item) where TAltKey : IOpEquals<T>, IHashable
{
if (mBuckets != null)
{
int32 hashCode = (int32)item.GetHashCode() & Lower31BitMask;
// see note at "HashSet" level describing why "- 1" appears in for loop
for (int32 i = mBuckets[hashCode % mBuckets.Count] - 1; i >= 0; i = mSlots[i].mNext)
{
if (mSlots[i].mHashCode == hashCode && /*m_comparer.Equals*/(mSlots[i].mValue == item))
{
return true;
}
}
}
// either m_buckets is null or wasn't found
return false;
}
/// Copy items in this hashset to array, starting at arrayIndex
/// @param array array to add items to
/// @param arrayIndex index to start at
public void CopyTo(T[] array, int32 arrayIndex)
{
CopyTo(array, arrayIndex, mCount);
}
bool Remove(T item, T* outValue)
{
if (mBuckets != null)
{
int32 hashCode = (int32)InternalGetHashCode(item);
int32 bucket = hashCode % (int32)mBuckets.Count;
int32 last = -1;
for (int32 i = mBuckets[bucket] - 1; i >= 0; last = i,i = mSlots[i].mNext)
{
if (mSlots[i].mHashCode == hashCode && /*m_comparer.Equals*/(mSlots[i].mValue == item))
{
if (last < 0)
{
// first iteration; update buckets
mBuckets[bucket] = mSlots[i].mNext + 1;
}
else
{
// subsequent iterations; update 'next' pointers
mSlots[last].mNext = mSlots[i].mNext;
}
if (outValue != null)
*outValue = mSlots[i].mValue;
mSlots[i].mHashCode = -1;
mSlots[i].mValue = default(T);
mSlots[i].mNext = mFreeList;
mCount--;
#if VERSION_HASHSET
mVersion++;
#endif
if (mCount == 0)
{
mLastIndex = 0;
mFreeList = -1;
}
else
{
mFreeList = i;
}
return true;
}
}
}
// either m_buckets is null or wasn't found
return false;
}
/// Remove item from this container
/// @param item item to remove
/// @return true if removed; false if not (i.e. if the item wasn't in the HashSet)
public bool Remove(T item)
{
return Remove(item, null);
}
/// Remove item from this container
/// @param item item to remove
/// @return .Ok(value) if removed, with 'value' being the stored value; .Err if not (i.e. if the item wasn't in the HashSet)
public Result<T> GetAndRemove(T item)
{
T value = ?;
if (!Remove(item, &value))
return .Err;
return .Ok(value);
}
/// Number of elements in this hashset
public int Count
{
get { return mCount; }
}
public bool IsEmpty
{
get
{
return mCount == 0;
}
}
/// Whether this is readonly
/*bool ICollection<T>.IsReadOnly
{
get { return false; }
}*/
#endregion
#region IEnumerable methods
public Enumerator GetEnumerator()
{
return Enumerator(this);
}
/*IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return new Enumerator(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(this);
} */
#endregion
#region ISerializable methods
/// Add item to this HashSet. Returns bool indicating whether item was added (won't be
/// added if already present)
/// @return true if added, false if already present
public bool Add(T item)
{
T* entryPtr;
return Add(item, out entryPtr);
}
public bool TryAdd(T item, out T* entryPtr)
{
return Add(item, out entryPtr);
}
public void CopyTo(T[] array) { CopyTo(array, 0, mCount); }
public void CopyTo(T[] array, int32 arrayIndex, int32 count)
{
if (array == null)
{
Runtime.FatalError("array");
}
Contract.EndContractBlock();
// check array index valid index into array
if (arrayIndex < 0)
{
//throw new ArgumentOutOfRangeException("arrayIndex", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNum));
Runtime.FatalError();
}
// also throw if count less than 0
if (count < 0)
{
//throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNum));
Runtime.FatalError();
}
// will array, starting at arrayIndex, be able to hold elements? Note: not
// checking arrayIndex >= array.Length (consistency with list of allowing
// count of 0; subsequent check takes care of the rest)
if (arrayIndex > array.Count || count > array.Count - arrayIndex)
{
//throw new ArgumentException(SR.GetString(SR.Arg_ArrayPlusOffTooSmall));
Runtime.FatalError();
}
int32 numCopied = 0;
for (int32 i = 0; i < mLastIndex && numCopied < count; i++)
{
if (mSlots[i].mHashCode >= 0)
{
array[arrayIndex + numCopied] = mSlots[i].mValue;
numCopied++;
}
}
}
/// Remove elements that match specified predicate. Returns the number of elements removed
public int32 RemoveWhere(Predicate<T> match)
{
if (match == null)
{
Runtime.FatalError("match");
}
Contract.EndContractBlock();
int32 numRemoved = 0;
for (int32 i = 0; i < mLastIndex; i++)
{
if (mSlots[i].mHashCode >= 0)
{
// cache value in case delegate removes it
T value = mSlots[i].mValue;
if (match(value))
{
// check again that remove actually removed it
if (Remove(value))
{
numRemoved++;
}
}
}
}
return numRemoved;
}
/// Gets the IEqualityComparer that is used to determine equality of keys for
/// the HashSet.
/*public IEqualityComparer<T> Comparer
{
get
{
return m_comparer;
}
} */
int32 GetPrimeish(int32 min)
{
// This is a minimal effort to help address-aligned dataa
return (min | 1);
}
int32 ExpandSize(int32 oldSize)
{
int32 newSize = 2 * oldSize;
return GetPrimeish(newSize);
}
/// Sets the capacity of this list to the size of the list (rounded up to nearest prime),
/// unless count is 0, in which case we release references.
///
/// This method can be used to minimize a list's memory overhead once it is known that no
/// new elements will be added to the list. To completely clear a list and release all
/// memory referenced by the list, execute the following statements:
///
/// list.Clear();
/// list.TrimExcess();
public void TrimExcess()
{
Debug.Assert(mCount >= 0, "mCount is negative");
if (mCount == 0)
{
// if count is zero, clear references
delete mBuckets;
mBuckets = null;
delete mSlots;
mSlots = null;
#if VERSION_HASHSET
mVersion++;
#endif
}
else
{
Debug.Assert(mBuckets != null, "mBuckets was null but mCount > 0");
// similar to IncreaseCapacity but moves down elements in case add/remove/etc
// caused fragmentation
int32 newSize = GetPrimeish(mCount);
Slot[] newSlots = new Slot[newSize];
int32[] newBuckets = new int32[newSize];
// move down slots and rehash at the same time. newIndex keeps track of current
// position in newSlots array
int32 newIndex = 0;
for (int32 i = 0; i < mLastIndex; i++)
{
if (mSlots[i].mHashCode >= 0)
{
newSlots[newIndex] = mSlots[i];
// rehash
int32 bucket = newSlots[newIndex].mHashCode % newSize;
newSlots[newIndex].mNext = newBuckets[bucket] - 1;
newBuckets[bucket] = newIndex + 1;
newIndex++;
}
}
Debug.Assert(newSlots.Count <= mSlots.Count, "capacity increased after TrimExcess");
mLastIndex = newIndex;
delete mSlots;
mSlots = newSlots;
delete mBuckets;
mBuckets = newBuckets;
mFreeList = -1;
}
}
#endregion
#region Helper methods
/// Initializes buckets and slots arrays. Uses suggested capacity by finding next prime
/// greater than or equal to capacity.
private void Initialize(int32 capacity)
{
Debug.Assert(mBuckets == null, "Initialize was called but mBuckets was non-null");
int32 size = GetPrimeish(capacity);
mBuckets = new int32[size];
mSlots = new Slot[size];
}
/// Expand to new capacity. New capacity is next prime greater than or equal to suggested
/// size. This is called when the underlying array is filled. This performs no
/// defragmentation, allowing faster execution; note that this is reasonable since
/// AddIfNotPresent attempts to insert new elements in re-opened spots.
private void IncreaseCapacity()
{
Debug.Assert(mBuckets != null, "IncreaseCapacity called on a set with no elements");
int32 newSize = ExpandSize(mCount);
if (newSize <= mCount)
{
//throw new ArgumentException(SR.GetString(SR.Arg_HSCapacityOverflow));
Runtime.FatalError();
}
// Able to increase capacity; copy elements to larger array and rehash
SetCapacity(newSize, false);
}
/// 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.
private void SetCapacity(int32 newSize, bool forceNewHashCodes)
{
//Contract.Assert(HashHelpers.IsPrime(newSize), "New size is not prime!");
Contract.Assert(mBuckets != null, "SetCapacity called on a set with no elements");
Slot[] newSlots = new Slot[newSize];
if (mSlots != null)
{
Array.Copy(mSlots, 0, newSlots, 0, mLastIndex);
}
if (forceNewHashCodes)
{
for (int32 i = 0; i < mLastIndex; i++)
{
if (newSlots[i].mHashCode != -1)
{
newSlots[i].mHashCode = (int32)InternalGetHashCode(newSlots[i].mValue);
}
}
}
int32[] newBuckets = new int32[newSize];
for (int32 i = 0; i < mLastIndex; i++)
{
int32 bucket = newSlots[i].mHashCode % newSize;
newSlots[i].mNext = newBuckets[bucket] - 1;
newBuckets[bucket] = i + 1;
}
delete mSlots;
mSlots = newSlots;
delete mBuckets;
mBuckets = newBuckets;
}
/// Adds value to HashSet if not contained already
/// @return true if added and false if already present
/// @param value value to find
/// @param entryPtr ponter to entry
public bool Add(T value, out T* entryPtr)
{
if (mBuckets == null)
{
Initialize(0);
}
int32 hashCode = (int32)InternalGetHashCode(value);
int32 bucket = hashCode % (int32)mBuckets.Count;
#if FEATURE_RANDOMIZED_STRING_HASHING && !FEATURE_NETCORE
int collisionCount = 0;
#endif
for (int32 i = mBuckets[hashCode % mBuckets.Count] - 1; i >= 0; i = mSlots[i].mNext)
{
if (mSlots[i].mHashCode == hashCode && /*m_comparer.Equals*/(mSlots[i].mValue == value))
{
entryPtr = &mSlots[i].mValue;
return false;
}
#if FEATURE_RANDOMIZED_STRING_HASHING && !FEATURE_NETCORE
collisionCount++;
#endif
}
int32 index;
if (mFreeList >= 0)
{
index = mFreeList;
mFreeList = mSlots[index].mNext;
}
else
{
if (mLastIndex == mSlots.Count)
{
IncreaseCapacity();
// this will change during resize
bucket = hashCode % (int32)mBuckets.Count;
}
index = mLastIndex;
mLastIndex++;
}
mSlots[index].mHashCode = hashCode;
mSlots[index].mValue = value;
mSlots[index].mNext = mBuckets[bucket] - 1;
mBuckets[bucket] = index + 1;
mCount++;
#if VERSION_HASHSET
mVersion++;
#endif
#if FEATURE_RANDOMIZED_STRING_HASHING && !FEATURE_NETCORE
if(collisionCount > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(m_comparer)) {
m_comparer = (IEqualityComparer<T>) HashHelpers.GetRandomizedEqualityComparer(m_comparer);
SetCapacity(m_buckets.Length, true);
}
#endif // FEATURE_RANDOMIZED_STRING_HASHING
entryPtr = &mSlots[index].mValue;
return true;
}
/// Checks if this contains of other's elements. Iterates over other's elements and
/// returns false as soon as it finds an element in other that's not in this.
/// Used by SupersetOf, ProperSupersetOf, and SetEquals.
/*private bool ContainsAllElements(IEnumerable<T> other)
{
foreach (T element in other)
{
if (!Contains(element))
{
return false;
}
}
return true;
}*/
/// Implementation Notes:
/// If other is a hashset and is using same equality comparer, then checking subset is
/// faster. Simply check that each element in this is in other.
///
/// Note: if other doesn't use same equality comparer, then Contains check is invalid,
/// which is why callers must take are of this.
///
/// If callers are concerned about whether this is a proper subset, they take care of that.
private bool IsSubsetOfHashSetWithSameEC(HashSet<T> other)
{
for (T item in this)
{
if (!other.Contains(item))
{
return false;
}
}
return true;
}
/// If other is a hashset that uses same equality comparer, intersect is much faster
/// because we can use other's Contains
private void IntersectWithHashSetWithSameEC(HashSet<T> other)
{
for (int32 i = 0; i < mLastIndex; i++)
{
if (mSlots[i].mHashCode >= 0)
{
T item = mSlots[i].mValue;
if (!other.Contains(item))
{
Remove(item);
}
}
}
}
/// 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.
/*private void IntersectWithEnumerable(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 = new int[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);
}
}
} */
/// Used internally by set operations which have to rely on bit array marking. This is like
/// Contains but returns index in slots array.
private int32 InternalIndexOf(T item)
{
Debug.Assert(mBuckets != null, "m_buckets was null; callers should check first");
int32 hashCode = (int32)InternalGetHashCode(item);
for (int32 i = mBuckets[hashCode % mBuckets.Count] - 1; i >= 0; i = mSlots[i].mNext)
{
if ((mSlots[i].mHashCode) == hashCode && /*m_comparer.Equals*/(mSlots[i].mValue == item))
{
return i;
}
}
// wasn't found
return -1;
}
/// if other is a set, we can assume it doesn't have duplicate elements, so use this
/// technique: if can't remove, then it wasn't present in this set, so add.
///
/// As with other methods, callers take care of ensuring that other is a hashset using the
/// same equality comparer.
private void SymmetricExceptWithUniqueHashSet(HashSet<T> other)
{
for (T item in other)
{
if (!Remove(item))
{
Add(item);
}
}
}
/// Implementation notes:
///
/// Used for symmetric except when other isn't a HashSet. This is more tedious because
/// other may contain duplicates. HashSet technique could fail in these situations:
/// 1. Other has a duplicate that's not in this: HashSet technique would add then
/// remove it.
/// 2. Other has a duplicate that's in this: HashSet technique would remove then add it
/// back.
/// In general, its presence would be toggled each time it appears in other.
///
/// This technique uses bit marking to indicate whether to add/remove the item. If already
/// present in collection, it will get marked for deletion. If added from other, it will
/// get marked as something not to remove.
///
/*private void SymmetricExceptWithEnumerable(IEnumerable<T> other)
{
int originalLastIndex = m_lastIndex;
int intArrayLength = BitHelper.ToIntArrayLength(originalLastIndex);
BitHelper itemsToRemove;
BitHelper itemsAddedFromOther;
if (intArrayLength <= StackAllocThreshold / 2)
{
int* itemsToRemovePtr = stackallocint[intArrayLength];
itemsToRemove = new BitHelper(itemsToRemovePtr, intArrayLength);
int* itemsAddedFromOtherPtr = stackallocint[intArrayLength];
itemsAddedFromOther = new BitHelper(itemsAddedFromOtherPtr, intArrayLength);
}
else
{
int[] itemsToRemoveArray = new int[intArrayLength];
itemsToRemove = new BitHelper(itemsToRemoveArray, intArrayLength);
int[] itemsAddedFromOtherArray = new int[intArrayLength];
itemsAddedFromOther = new BitHelper(itemsAddedFromOtherArray, intArrayLength);
}
foreach (T item in other)
{
int location = 0;
bool added = AddOrGetLocation(item, out location);
if (added)
{
// wasn't already present in collection; flag it as something not to remove
// *NOTE* if location is out of range, we should ignore. BitHelper will
// detect that it's out of bounds and not try to mark it. But it's
// expected that location could be out of bounds because adding the item
// will increase m_lastIndex as soon as all the free spots are filled.
itemsAddedFromOther.MarkBit(location);
}
else
{
// already there...if not added from other, mark for remove.
// *NOTE* Even though BitHelper will check that location is in range, we want
// to check here. There's no point in checking items beyond originalLastIndex
// because they could not have been in the original collection
if (location < originalLastIndex && !itemsAddedFromOther.IsMarked(location))
{
itemsToRemove.MarkBit(location);
}
}
}
// if anything marked, remove it
for (int i = 0; i < originalLastIndex; i++)
{
if (itemsToRemove.IsMarked(i))
{
Remove(m_slots[i].value);
}
}
}*/
/// Add if not already in hashset. Returns an out param indicating index where added. This
/// is used by SymmetricExcept because it needs to know the following things:
/// - whether the item was already present in the collection or added from other
/// - where it's located (if already present, it will get marked for removal, otherwise
/// marked for keeping)
private bool AddOrGetLocation(T value, out int32 location)
{
Debug.Assert(mBuckets != null, "Buckets is null, callers should have checked");
int32 hashCode = (int32)InternalGetHashCode(value);
int32 bucket = hashCode % (int32)mBuckets.Count;
for (int32 i = mBuckets[hashCode % mBuckets.Count] - 1; i >= 0; i = mSlots[i].mNext)
{
if (mSlots[i].mHashCode == hashCode && /*m_comparer.Equals*/(mSlots[i].mValue == value))
{
location = i;
return false; //already present
}
}
int32 index;
if (mFreeList >= 0)
{
index = mFreeList;
mFreeList = mSlots[index].mNext;
}
else
{
if (mLastIndex == mSlots.Count)
{
IncreaseCapacity();
// this will change during resize
bucket = hashCode % (int32)mBuckets.Count;
}
index = mLastIndex;
mLastIndex++;
}
mSlots[index].mHashCode = hashCode;
mSlots[index].mValue = value;
mSlots[index].mNext = mBuckets[bucket] - 1;
mBuckets[bucket] = index + 1;
mCount++;
#if VERSION_HASHSET
mVersion++;
#endif
location = index;
return true;
}
/// Determines counts that can be used to determine equality, subset, and superset. This
/// is only used when other is an IEnumerable and not a HashSet. If other is a HashSet
/// these properties can be checked faster without use of marking because we can assume
/// other has no duplicates.
///
/// The following count checks are performed by callers:
/// 1. Equals: checks if unfoundCount = 0 and uniqueFoundCount = m_count; i.e. everything
/// in other is in this and everything in this is in other
/// 2. Subset: checks if unfoundCount >= 0 and uniqueFoundCount = m_count; i.e. other may
/// have elements not in this and everything in this is in other
/// 3. Proper subset: checks if unfoundCount > 0 and uniqueFoundCount = m_count; i.e
/// other must have at least one element not in this and everything in this is in other
/// 4. Proper superset: checks if unfound count = 0 and uniqueFoundCount strictly less
/// than m_count; i.e. everything in other was in this and this had at least one element
/// not contained in other.
///
/// An earlier implementation used delegates to perform these checks rather than returning
/// an ElementCount struct; however this was changed due to the perf overhead of delegates.
/// @param returnIfUnfound Allows us to finish faster for equals and proper superset
/// because unfoundCount must be 0.
/*private 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 = new int[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;
}*/
/// Copies this to an array. Used for DebugView
internal T[] ToArray()
{
T[] newArray = new T[Count];
CopyTo(newArray);
return newArray;
}
/// Internal method used for HashSetEqualityComparer. Compares set1 and set2 according
/// to specified comparer.
///
/// Because items are hashed according to a specific equality comparer, we have to resort
/// to n^2 search if they're using different equality comparers.
/*internal static bool HashSetEquals(HashSet<T> set1, HashSet<T> set2, IEqualityComparer<T> comparer)
{
// handle null cases first
if (set1 == null)
{
return (set2 == null);
}
else if (set2 == null)
{
// set1 != null
return false;
}
// all comparers are the same; this is faster
if (AreEqualityComparersEqual(set1, set2))
{
if (set1.Count != set2.Count)
{
return false;
}
// suffices to check subset
foreach (T item in set2)
{
if (!set1.Contains(item))
{
return false;
}
}
return true;
}
else
{ // n^2 search because items are hashed according to their respective ECs
foreach (T set2Item in set2)
{
bool found = false;
foreach (T set1Item in set1)
{
if (comparer.Equals(set2Item, set1Item))
{
found = true;
break;
}
}
if (!found)
{
return false;
}
}
return true;
}
}*/
/// Checks if equality comparers are equal. This is used for algorithms that can
/// speed up if it knows the other item has unique elements. I.e. if they're using
/// different equality comparers, then uniqueness assumption between sets break.
/*private static bool AreEqualityComparersEqual(HashSet<T> set1, HashSet<T> set2)
{
return set1.Comparer.Equals(set2.Comparer);
}*/
/// Workaround Comparers that throw ArgumentNullException for GetHashCode(null).
/// @return hash code
private int InternalGetHashCode(T item)
{
if (item == null)
{
return 0;
}
return item.GetHashCode() & Lower31BitMask;
}
#endregion
// used for set checking operations (using enumerables) that rely on counting
internal struct ElementCount
{
internal int32 mUniqueCount;
internal int32 mUnfoundCount;
}
internal struct Slot
{
internal int32 mHashCode; // Lower 31 bits of hash code, -1 if unused
internal T mValue;
internal int32 mNext; // Index of next entry, -1 if last
}
public struct Enumerator : IEnumerator<T>
{
private HashSet<T> mSet;
private int32 mIndex;
#if VERSION_HASHSET
private int32 mVersion;
#endif
private T mCurrent;
internal this(HashSet<T> set)
{
this.mSet = set;
mIndex = 0;
#if VERSION_HASHSET
mVersion = set.mVersion;
#endif
mCurrent = default;
}
#if VERSION_HASHSET
void CheckVersion()
{
if (mVersion != mSet.mVersion)
Runtime.FatalError(cVersionError);
}
#endif
public void Dispose()
{
}
public bool MoveNext() mut
{
#if VERSION_HASHSET
CheckVersion();
#endif
while (mIndex < mSet.mLastIndex)
{
if (mSet.mSlots[mIndex].mHashCode >= 0)
{
mCurrent = mSet.mSlots[mIndex].mValue;
mIndex++;
return true;
}
mIndex++;
}
mIndex = mSet.mLastIndex + 1;
mCurrent = default(T);
return false;
}
public T Current
{
get
{
return mCurrent;
}
}
public void Reset() mut
{
#if VERSION_HASHSET
CheckVersion();
#endif
mIndex = 0;
mCurrent = default(T);
}
public Result<T> GetNext() mut
{
if (!MoveNext())
return .Err;
return Current;
}
}
}
}