A Small Segmented Set Class
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
/// <summary>
/// Date: 10/03/20 **MJS**
/// Customer set using indexed hash buckets.
/// Possible uses less memory then standard hashset, with very similar search performance.
/// </summary>
/// <typeparam name="T"></typeparam>
[DebuggerDisplay("Count = {" + nameof(Count) + "}")]
[Serializable]
public class MiniSet<T> : IEnumerable<T>
{
private Bucket[] _buckets;
private SizeHelper<T> _newSize = new SizeHelper<T>();
internal IEqualityComparer<T> Comparer;
public int Count;
public int ReSizes;
public MiniSet(int size) : this(size, EqualityComparer<T>.Default)
{
}
public MiniSet(int size, IEqualityComparer<T> comparer)
{
if (comparer == null)
comparer = EqualityComparer<T>.Default;
Comparer = comparer;
_buckets = new Bucket[size];
Count = 0;
ReSizes = 0;
}
public MiniSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
{
if (comparer == null)
comparer = EqualityComparer<T>.Default;
Comparer = comparer;
if (!(collection is ICollection<T> coll))
return;
Count = coll.Count;
_buckets = new Bucket[Count];
UnionWith(coll);
}
public int ElementCount => GetElementCount();
public int NumberOfEmptyBuckets => GetNumberOfEmptyBuckets();
public (int mDepth, int index) MaximumBucketDepth => GetMaximumBucketDepth();
public float LoadRatio => GetLoadRatio();
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Clear()
{
_buckets.Clear();
}
public void UnionWith(IEnumerable<T> other)
{
if (other == null)
throw new Exception("The other set must not be null.");
foreach (var obj in other)
Add(obj);
}
public bool Add(T item)
{
EnsureSize();
var hashCode = Comparer.GetHashCode(item) & int.MaxValue;
if (FindEntry(item, hashCode).APos != -1)
return false;
var pos = hashCode % _buckets.Length;
if (_buckets[pos] == null)
_buckets[pos] = new Bucket();
_buckets[pos].Add(item);
Count++;
return true;
}
public int AddRange(IEnumerable<T> items)
{
return items.Sum(i => !Add(i) ? 0 : 1);
}
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 APos, int BPos) FindEntry(T item, int hashCode)
{
if (Count == 0)
return (-1, -1);
var aPos = hashCode % _buckets.Length;
var bPos = 0;
if (_buckets[aPos] == null)
_buckets[aPos] = new Bucket();
foreach (var i in _buckets[aPos].Values)
{
if (Comparer.Equals(i, item))
return (aPos, bPos);
bPos++;
}
return (-1, -1);
}
private void EnsureSize()
{
if (Count >= _buckets.Length)
{
ReSizes++;
var cArray = ToArray();
_buckets = new Bucket[_newSize.GetNewSize(_buckets.Length)];
foreach (var i in cArray)
{
var hashCode = Comparer.GetHashCode(i) & int.MaxValue;
var pos = hashCode % _buckets.Length;
if (_buckets[pos] == null)
_buckets[pos] = new Bucket();
_buckets[pos].Add(i);
}
}
}
public bool Contains(T item)
{
var hashCode = Comparer.GetHashCode(item) & int.MaxValue;
return FindEntry(item, hashCode).APos != -1;
}
public IEnumerator<T> GetEnumerator()
{
for (var i = 0; i < _buckets.Length; i++)
if (_buckets[i] != null)
for (var j = 0; j < _buckets[i].Count; ++j)
yield return _buckets[i].Values[j];
}
public int GetElementCount()
{
var count = 0;
for (var i = 0; i < _buckets.Length; i++)
if (_buckets[i] != null)
{
var c = _buckets[i].Count;
count += c;
}
return count;
}
public int GetNumberOfEmptyBuckets()
{
var count = 0;
for (var i = 0; i < _buckets.Length; i++)
if (_buckets[i] == null)
count++;
return count;
}
public int GetNumberOfFilledBuckets()
{
var count = 0;
for (var i = 0; i < _buckets.Length; i++)
if (_buckets[i] != null)
count++;
return count;
}
public (int mDepth, int index) GetMaximumBucketDepth()
{
var max = 0;
var j = 0;
for (var i = 0; i < _buckets.Length; i++)
if (_buckets[i] != null)
{
var count = _buckets[i].Count;
if (count > max)
{
max = count;
j = i;
}
}
return (max, j);
}
public KeyValuePair<int, int>[] BucketDepthList => GetBucketDepthList();
public KeyValuePair<int, int>[] GetBucketDepthList()
{
var bdic = new Dictionary<int, int>();
for (var i = 0; i < _buckets.Length; i++)
if (_buckets[i] != null)
{
var count = _buckets[i].Count;
if (!bdic.ContainsKey(count))
{
bdic.Add(count, 0);
bdic[count]++;
}
else
{
bdic[count]++;
}
}
return bdic.OrderByDescending(x=>x.Value).ToArray();
}
public float GetLoadRatio()
{
var x = Count;
var y = _buckets.Length;
var r = x / (float) y;
return r;
}
internal class Bucket
{
public int Count;
public T[] Values;
public Bucket()
{
Values = new T[11];
Count = 0;
}
public void Add(T item)
{
if (Count >= Values.Length)
{
var ta = new T[Values.Length+3];
Array.Copy(Values, 0, ta, 0, Count);
Values = ta;
}
Values[Count++] = item;
}
}
}