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; } } }