using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Digitigrade.Collections { /// /// Variant on Witold Litwin's 1980 sorted linear hash table. This is here to /// prove correctness of my linear hashing logic before working on the linear bloom. /// NB: This is not-thread safe, and relies on having an exclusive writer or multiple readers /// managed by external code. /// /// The type of items to store in the collection [DebuggerDisplay("Count = {count}")] public class SortedLinearHashTable : ICollection { public const int DefaultSize = 4; public const int MinimalSize = 2; internal IEqualityComparer comparer; internal SinglyLinkedList[] data; internal int count; private int mask; private int Stride { get { return (mask + 1) / 2; } } #region Constructors public SortedLinearHashTable() : this(PreparedEqualityComparer.Default, DefaultSize) {} public SortedLinearHashTable(IEqualityComparer comparer) : this(comparer, DefaultSize) {} public SortedLinearHashTable(IEqualityComparer comparer, int initialSize) { this.comparer = comparer; this.data = new SinglyLinkedList[Math.Max(initialSize,MinimalSize)]; } #endregion #region ICollection Members public void Add(T item) { AddBucket(); data[GetHashCode(item)].Add(item); } public void Clear() { this.data = new SinglyLinkedList[MinimalSize]; this.count = 0; this.mask = 0; } public bool Contains(T item) { return data[GetHashCode(item)].Contains(item, comparer); } internal bool Any(Func predicate, int hash) { return GetSlot(hash).Any(predicate); } public void CopyTo(T[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException("array"); if (arrayIndex < 0 || arrayIndex > array.Length) throw new ArgumentOutOfRangeException("arrayIndex"); if (arrayIndex + count > array.Length) throw new ArgumentException("arrayIndex + count exceeds array length"); for (var i = 0; i < count; i++) { foreach (T item in data[i]) { array[arrayIndex++] = item; } } } public int Count { get { return count; } } public bool IsReadOnly { get { return false; } } public bool Remove(T item) { bool removed = data[GetHashCode(item)].Remove(item,comparer); RemoveBucket(); return removed; } public int Remove(Func predicate) { int debt = 0; for (int i = 0; i < count; ++i) { debt += data[i].Remove(predicate); } int removed = debt; while (debt-- > 0) RemoveBucket(); return removed; } internal int Remove(Func predicate, int hash) { int debt = GetSlot(hash).Remove(predicate); int removed = debt; while (debt-- > 0) RemoveBucket(); return removed; } #endregion #region IEnumerable Members public IEnumerator GetEnumerator() { for (var i = 0; i < count; i++) { foreach (T item in data[i]) { yield return item; } } } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region Implementation Details internal void RemoveBucket() { Debug.Assert(count > 0); if (--count < Stride) { mask -= Stride; } // 0,1,2,3,4,5,6,7,8... count // 0,1,3,3,7,7,7,7,15.. mask // 0,1,2,2,4,4,4,4,8... stride SinglyLinkedList left = data[count - Stride]; foreach (T item in data[count]) { left.Add(item); } data[count] = null; // prevent dangling references if (data.Length > MinimalSize && count * 4 < data.Length) { // if we can shrink and we've lost a lot of mass, then do so. // TODO: shrink more if possible int newSize = Math.Max(data.Length / 2, MinimalSize); SinglyLinkedList[] newData = new SinglyLinkedList[newSize]; Array.Copy(data, newData, newSize); data = newData; } } internal void AddBucket() { // make sure we allocate room if we need it if (count == data.Length) { SinglyLinkedList[] newData = new SinglyLinkedList[count * 2]; Array.Copy(data, newData, count); data = newData; } // create the new bucket SinglyLinkedList rightList = data[count] = new SinglyLinkedList(); if (count != 0) { int left = count - Stride; SinglyLinkedList oldList = data[left]; SinglyLinkedList leftList = data[left] = new SinglyLinkedList(); foreach (T element in oldList) { int hashCode = comparer.GetHashCode(element); if ((hashCode & mask) == left) { leftList.Add(element); } else { Debug.Assert((hashCode & mask) == count); rightList.Add(element); } } } // 0,1,2,3,4,5,6,7,8... count mask |= ++count; // 0,1,3,3,7,7,7,7,15.. mask //stride = (mask + 1) >> 1; // 0,1,2,2,4,4,4,4,8... stride } internal int GetHashCode(T item) { int hashCode = comparer.GetHashCode(item) & mask; if (hashCode >= count) hashCode -= Stride; return hashCode; } internal SinglyLinkedList GetSlot(int hashCode) { hashCode &= mask; if (hashCode >= count) hashCode -= Stride; return data[hashCode]; } internal void Audit() { Debug.Assert(data.Length == MinimalSize || count < 4 * data.Length); for (int i = 0; i < count; i++) { foreach (var item in data[i]) { Debug.Assert(GetHashCode(item) == i); } } } #endregion } }