You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Zero/ZeroLevel.HNSW/Services/BinaryHeap.cs

163 lines
4.8 KiB

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace ZeroLevel.HNSW
{
/// <summary>
/// Binary heap wrapper around the <see cref="IList{T}"/>
/// It's a max-heap implementation i.e. the maximum element is always on top.
/// </summary>
/// <typeparam name="T">The type of the items in the source list.</typeparam>
public class BinaryHeap :
IEnumerable<(int, float)>
{
private static BinaryHeap _empty = new BinaryHeap();
public static BinaryHeap Empty => _empty;
private readonly List<(int, float)> _data;
private bool _frozen = false;
public (int, float) Nearest => _data[_data.Count - 1];
public (int, float) Farthest => _data[0];
public (int, float) PopNearest()
{
if (this._data.Any())
{
var result = this._data[this._data.Count - 1];
this._data.RemoveAt(this._data.Count - 1);
return result;
}
return (-1, -1);
}
public (int, float) PopFarthest()
{
if (this._data.Any())
{
var result = this._data.First();
this._data[0] = this._data.Last();
this._data.RemoveAt(this._data.Count - 1);
this.SiftDown(0);
return result;
}
return (-1, -1);
}
public int Count => _data.Count;
public void Clear() => _data.Clear();
/// <summary>
/// Initializes a new instance of the <see cref="BinaryHeap{T}"/> class.
/// </summary>
/// <param name="buffer">The buffer to store heap items.</param>
public BinaryHeap(int k = -1, bool frozen = false)
{
_frozen = frozen;
if (k > 0)
_data = new List<(int, float)>(k);
else
_data = new List<(int, float)>();
}
/// <summary>
/// Pushes item to the heap.
/// </summary>
/// <param name="item">The item to push.</param>
public void Push(int item, float distance)
{
this._data.Add((item, distance));
this.SiftUp(this._data.Count - 1);
}
/// <summary>
/// Pops the item from the heap.
/// </summary>
/// <returns>The popped item.</returns>
public (int, float) Pop()
{
if (this._data.Any())
{
var result = this._data.First();
this._data[0] = this._data.Last();
this._data.RemoveAt(this._data.Count - 1);
this.SiftDown(0);
return result;
}
throw new InvalidOperationException("Heap is empty");
}
/// <summary>
/// Restores the heap property starting from i'th position down to the bottom
/// given that the downstream items fulfill the rule.
/// </summary>
/// <param name="i">The position of item where heap property is violated.</param>
private void SiftDown(int i)
{
while (i < this._data.Count)
{
int l = (2 * i) + 1;
int r = l + 1;
if (l >= this._data.Count)
{
break;
}
int m = ((r < this._data.Count) && this._data[l].Item2 < this._data[r].Item2) ? r : l;
if (this._data[m].Item2 <= this._data[i].Item2)
{
break;
}
this.Swap(i, m);
i = m;
}
}
/// <summary>
/// Restores the heap property starting from i'th position up to the head
/// given that the upstream items fulfill the rule.
/// </summary>
/// <param name="i">The position of item where heap property is violated.</param>
private void SiftUp(int i)
{
while (i > 0)
{
int p = (i - 1) / 2;
if (this._data[i].Item2 <= this._data[p].Item2)
{
break;
}
this.Swap(i, p);
i = p;
}
}
/// <summary>
/// Swaps items with the specified indicies.
/// </summary>
/// <param name="i">The first index.</param>
/// <param name="j">The second index.</param>
private void Swap(int i, int j)
{
var temp = this._data[i];
this._data[i] = this._data[j];
this._data[j] = temp;
}
public IEnumerator<(int, float)> GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _data.GetEnumerator();
}
}
}

Powered by TurnKey Linux.