diff --git a/ZeroLevel.HNSW/Services/BinaryHeap.cs b/ZeroLevel.HNSW/Services/BinaryHeap.cs new file mode 100644 index 0000000..675f541 --- /dev/null +++ b/ZeroLevel.HNSW/Services/BinaryHeap.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ZeroLevel.HNSW.Services +{ + /// + /// Binary heap wrapper around the + /// It's a max-heap implementation i.e. the maximum element is always on top. + /// But the order of elements can be customized by providing instance. + /// + /// The type of the items in the source list. + public class BinaryHeap + { + /// + /// Initializes a new instance of the class. + /// + /// The buffer to store heap items. + public BinaryHeap(IList buffer) + : this(buffer, Comparer.Default) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The buffer to store heap items. + /// The comparer which defines order of items. + public BinaryHeap(IList buffer, IComparer comparer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + this.Buffer = buffer; + this.Comparer = comparer; + for (int i = 1; i < this.Buffer.Count; ++i) + { + this.SiftUp(i); + } + } + + /// + /// Gets the heap comparer. + /// + public IComparer Comparer { get; private set; } + + /// + /// Gets the buffer of the heap. + /// + public IList Buffer { get; private set; } + + /// + /// Pushes item to the heap. + /// + /// The item to push. + public void Push(T item) + { + this.Buffer.Add(item); + this.SiftUp(this.Buffer.Count - 1); + } + + /// + /// Pops the item from the heap. + /// + /// The popped item. + public T Pop() + { + if (this.Buffer.Any()) + { + var result = this.Buffer.First(); + + this.Buffer[0] = this.Buffer.Last(); + this.Buffer.RemoveAt(this.Buffer.Count - 1); + this.SiftDown(0); + + return result; + } + + throw new InvalidOperationException("Heap is empty"); + } + + /// + /// Restores the heap property starting from i'th position down to the bottom + /// given that the downstream items fulfill the rule. + /// + /// The position of item where heap property is violated. + private void SiftDown(int i) + { + while (i < this.Buffer.Count) + { + int l = (2 * i) + 1; + int r = l + 1; + if (l >= this.Buffer.Count) + { + break; + } + + int m = r < this.Buffer.Count && this.Comparer.Compare(this.Buffer[l], this.Buffer[r]) < 0 ? r : l; + if (this.Comparer.Compare(this.Buffer[m], this.Buffer[i]) <= 0) + { + break; + } + + this.Swap(i, m); + i = m; + } + } + + /// + /// Restores the heap property starting from i'th position up to the head + /// given that the upstream items fulfill the rule. + /// + /// The position of item where heap property is violated. + private void SiftUp(int i) + { + while (i > 0) + { + int p = (i - 1) / 2; + if (this.Comparer.Compare(this.Buffer[i], this.Buffer[p]) <= 0) + { + break; + } + + this.Swap(i, p); + i = p; + } + } + + /// + /// Swaps items with the specified indicies. + /// + /// The first index. + /// The second index. + private void Swap(int i, int j) + { + var temp = this.Buffer[i]; + this.Buffer[i] = this.Buffer[j]; + this.Buffer[j] = temp; + } + } +}