Single Array HashSet Class
Uses only a single array for tracking and storage.
Updated: July-11,2021
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
[DebuggerDisplay("Count = {Count}")]
public class BucketHashSet3<T> : IEnumerable
{
private Bucket[] _buckets;
internal IEqualityComparer<T> Comparer;
private int HiBucketCount;
public BucketHashSet3() : this(1_000_000, 10, null)
{
}
public BucketHashSet3(int sizeHint, int sizeHintBucket) : this(sizeHint, sizeHintBucket, null)
{
}
public BucketHashSet3(int sizeHint, int sizeHintBucket, IEqualityComparer<T> comparer)
{
if (comparer == null)
comparer = EqualityComparer<T>.Default;
Comparer = comparer;
_buckets = new Bucket[sizeHint];
Count = 0;
HiBucketCount = 0;
for (var i = 0; i < _buckets.Length; ++i)
_buckets[i].Allocate(sizeHintBucket);
}
public int Count
{
get;
private set;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Zero()
{
_buckets = null;
Count = 0;
}
public bool Add(T item)
{
var hashCode = Comparer.GetHashCode(item) & int.MaxValue;
var apos = FindEntry(item, hashCode);
if (apos != -1)
return false;
var pos = hashCode % _buckets.Length;
_buckets[pos].Add(item);
Count++;
return true;
}
public T[] ToArray()
{
var newArray = new T[Count];
using (var en = GetEnumerator())
{
var ptr = 0;
while (en.MoveNext())
{
var value = en.Current;
if (value == null)
break;
newArray[ptr++] = value;
}
return newArray;
}
}
private int FindEntry(T item, int hashCode)
{
if (Count == 0)
return -1;
var Pos = hashCode % _buckets.Length;
if (_buckets[Pos].Count == 0)
return -1;
var cnt = _buckets[Pos].Count;
if (cnt > HiBucketCount)
HiBucketCount = cnt;
if (HiBucketCount >= 100)
{
Resize();
HiBucketCount = 0;
return FindEntry(item, hashCode);
}
for (var i = 0; i < cnt; ++i)
{
var v = _buckets[Pos].Values[i];
if (Comparer.Equals(v, item))
return Pos;
}
return -1;
}
private void Resize()
{
var cArray = ToArray();
var newSize = _buckets.Length << 1;
_buckets = new Bucket[newSize];
for (var i = 0; i < newSize; ++i)
_buckets[i].Allocate();
foreach (var i in cArray)
_buckets[BucketPosition(i)].Add(i);
}
private int BucketPosition(T item)
{
var hashCode = Comparer.GetHashCode(item) & int.MaxValue;
var pos = hashCode % _buckets.Length;
return pos;
}
public bool Contains(T item)
{
var hashCode = Comparer.GetHashCode(item) & int.MaxValue;
return FindEntry(item, hashCode) != -1;
}
public IEnumerator<T> GetEnumerator()
{
return GetEnum();
}
public IEnumerator<T> GetEnum()
{
for (var i = 0; i < _buckets.Length; i++)
for (var j = 0; j < _buckets[i].Count; ++j)
yield return _buckets[i].Values[j];
}
internal struct Bucket
{
public int Count;
public T[] Values;
public void Add(T item)
{
if (Count >= Values.Length)
Array.Resize(ref Values, Values.Length << 1);
Values[Count++] = item;
}
public void Allocate(int size = 10)
{
Values = new T[size];
}
}
}