added RcBinaryMinHeapTest

This commit is contained in:
ikpil 2024-08-04 18:03:01 +09:00
parent 8a655528c3
commit 89214accfb
4 changed files with 343 additions and 205 deletions

View File

@ -1,161 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
namespace DotRecast.Core.Collections
{
public sealed class RcBinaryHeap<T>
{
public int Count => _count;
public int Capacity => _values.Length;
public T this[int index] => _values[index];
private T[] _values;
private int _count;
private Comparison<T> _comparision;
public RcBinaryHeap(Comparison<T> comparison) : this(8, comparison)
{
}
public RcBinaryHeap(int capacity, Comparison<T> comparison)
{
if (capacity <= 0)
throw new ArgumentException("capacity must greater than zero");
_values = new T[capacity];
_comparision = comparison;
_count = 0;
}
public void Push(T val)
{
EnsureCapacity();
_values[_count++] = val;
UpHeap(_count - 1);
}
public T Pop()
{
if (_count == 0)
{
Throw();
static void Throw() =>
throw new InvalidOperationException("no element to pop");
}
Swap(0, --_count);
DownHeap(1);
return _values[_count];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Top()
{
return _values[0];
}
public void Modify(T node)
{
for (int i = 0; i < _count; i++)
{
if (_values[i].Equals(node))
{
UpHeap(i);
return;
}
}
}
public void Clear()
{
Array.Clear(_values, 0, _count);
_count = 0;
}
public void FastClear()
{
_count = 0;
}
public T[] ToArray()
{
var copy = new T[_count];
Array.Copy(_values, copy, _count);
return copy;
}
public void ReBuild()
{
for (int i = _count / 2; i >= 1; i--)
{
DownHeap(i);
}
}
private void EnsureCapacity()
{
if (_values.Length <= _count)
{
var newValues = new T[Capacity * 2];
Array.Copy(_values, newValues, _count);
_values = newValues;
}
}
private void UpHeap(int i)
{
int p = (i - 1) / 2;
while (p >= 0)
{
if (_comparision(_values[p], _values[i]) <= 0)
break;
Swap(p, i);
i = p;
p = (i - 1) / 2;
}
}
private void DownHeap(int i)
{
T d = _values[i - 1];
int child;
while (i <= _count / 2)
{
child = i * 2;
if (child < _count && _comparision(_values[child - 1], _values[child]) > 0)
child++;
if (_comparision(d, _values[child - 1]) <= 0)
break;
_values[i - 1] = _values[child - 1];
i = child;
}
_values[i - 1] = d;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Swap(int x, int y)
{
if (x == y)
return;
(_values[y], _values[x]) = (_values[x], _values[y]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsEmpty()
{
return _count == 0;
}
}
}

View File

@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace DotRecast.Core.Collections
{
public sealed class RcBinaryMinHeap<T>
{
private readonly List<T> _items;
private readonly Comparison<T> _comparision;
public int Count => _items.Count;
public int Capacity => _items.Capacity;
public RcBinaryMinHeap(Comparison<T> comparision)
{
_items = new List<T>();
_comparision = comparision;
}
public RcBinaryMinHeap(int capacity, Comparison<T> comparison) : this(comparison)
{
if (capacity <= 0)
throw new ArgumentException("capacity must greater than zero");
_items = new List<T>(capacity);
_comparision = comparison;
}
public void Push(T val)
{
_items.Add(val);
SiftUp(_items.Count - 1);
}
public T Pop()
{
var min = Peek();
RemoveMin();
return min;
}
private void RemoveMin()
{
if (_items.Count == 0)
{
Throw();
static void Throw() => throw new InvalidOperationException("no element to pop");
}
int last = _items.Count - 1;
Swap(0, last);
_items.RemoveAt(last);
MinHeapify(0, last - 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Top()
{
return _items[0];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Peek()
{
if (IsEmpty())
{
throw new Exception("Heap is empty.");
}
return _items[0];
}
public bool Modify(T node)
{
for (int i = 0; i < _items.Count; i++)
{
if (_items[i].Equals(node))
{
SiftUp(i);
return true;
}
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
_items.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsEmpty()
{
return 0 == _items.Count;
}
private void SiftUp(int nodeIndex)
{
int parent = (nodeIndex - 1) / 2;
while (_comparision.Invoke(_items[nodeIndex], _items[parent]) < 0)
{
Swap(parent, nodeIndex);
nodeIndex = parent;
parent = (nodeIndex - 1) / 2;
}
}
private void MinHeapify(int nodeIndex, int lastIndex)
{
int left = (nodeIndex * 2) + 1;
int right = left + 1;
int smallest = nodeIndex;
if (left <= lastIndex && _comparision.Invoke(_items[left], _items[nodeIndex]) < 0)
smallest = left;
if (right <= lastIndex && _comparision.Invoke(_items[right], _items[smallest]) < 0)
smallest = right;
if (smallest == nodeIndex)
return;
Swap(nodeIndex, smallest);
MinHeapify(smallest, lastIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Swap(int x, int y)
{
if (x == y)
return;
(_items[y], _items[x]) = (_items[x], _items[y]);
}
public T[] ToArray()
{
return _items.ToArray();
}
public List<T> ToList()
{
return new List<T>(_items);
}
}
}

View File

@ -52,9 +52,9 @@ public class PriorityQueueBenchmarks
{ {
[Params(10, 100, 1000, 10000)] public int Count; [Params(10, 100, 1000, 10000)] public int Count;
private RcSortedQueue<Node> _rcQueue; private RcSortedQueue<Node> _sq;
private RcBinaryHeap<Node> _heap; private RcBinaryMinHeap<Node> _bmHeap;
private PriorityQueue<Node, Node> _pqueue; private PriorityQueue<Node, Node> _pq;
private float[] _priority; private float[] _priority;
@ -75,26 +75,24 @@ public class PriorityQueueBenchmarks
return x.id.CompareTo(y.id); return x.id.CompareTo(y.id);
} }
_rcQueue = new(Comp); _sq = new(Comp);
_heap = new(Count, Comp); _bmHeap = new(Count, Comp);
_pqueue = new(Count, Comparer<Node>.Create(Comp)); _pq = new(Count, Comparer<Node>.Create(Comp));
_priority = new float[Count]; _priority = new float[Count];
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
_priority[i] = (float)Random.Shared.NextDouble() * 100f; _priority[i] = (float)Random.Shared.NextDouble() * 100f;
} }
Console.WriteLine("111");
} }
[Benchmark] [Benchmark]
public void Enqueue_rcQueue() public void Enqueue_RcSortedQueue()
{ {
_rcQueue.Clear(); _sq.Clear();
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
_rcQueue.Enqueue(new Node _sq.Enqueue(new Node
{ {
id = i, id = i,
total = _priority[i], total = _priority[i],
@ -103,12 +101,12 @@ public class PriorityQueueBenchmarks
} }
[Benchmark] [Benchmark]
public void Enqueue_heap() public void Enqueue_RcBinaryMinHeap()
{ {
_heap.Clear(); _bmHeap.Clear();
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
_heap.Push(new Node _bmHeap.Push(new Node
{ {
id = i, id = i,
total = _priority[i], total = _priority[i],
@ -117,9 +115,9 @@ public class PriorityQueueBenchmarks
} }
[Benchmark] [Benchmark]
public void Enqueue_pqueue() public void Enqueue_PriorityQueue()
{ {
_pqueue.Clear(); _pq.Clear();
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
var node = new Node var node = new Node
@ -127,52 +125,52 @@ public class PriorityQueueBenchmarks
id = i, id = i,
total = _priority[i], total = _priority[i],
}; };
_pqueue.Enqueue(node, node); _pq.Enqueue(node, node);
} }
} }
[Benchmark] [Benchmark]
public void DequeueAll_rcQueue() public void DequeueAll_RcSortedQueue()
{ {
_rcQueue.Clear(); _sq.Clear();
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
_rcQueue.Enqueue(new Node _sq.Enqueue(new Node
{ {
id = i, id = i,
total = _priority[i], total = _priority[i],
}); });
} }
while (_rcQueue.Count() > 0) while (_sq.Count() > 0)
{ {
_rcQueue.Dequeue(); _sq.Dequeue();
} }
} }
[Benchmark] [Benchmark]
public void DequeueAll_heap() public void DequeueAll_RcBinaryMinHeap()
{ {
_heap.Clear(); _bmHeap.Clear();
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
_heap.Push(new Node _bmHeap.Push(new Node
{ {
id = i, id = i,
total = _priority[i], total = _priority[i],
}); });
} }
while (_heap.Count > 0) while (_bmHeap.Count > 0)
{ {
_heap.Pop(); _bmHeap.Pop();
} }
} }
[Benchmark] [Benchmark]
public void DequeueAll_pqueue() public void DequeueAll_PriorityQueue()
{ {
_pqueue.Clear(); _pq.Clear();
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
var node = new Node var node = new Node
@ -180,52 +178,52 @@ public class PriorityQueueBenchmarks
id = i, id = i,
total = _priority[i], total = _priority[i],
}; };
_pqueue.Enqueue(node, node); _pq.Enqueue(node, node);
} }
while (_pqueue.Count > 0) while (_pq.Count > 0)
{ {
_pqueue.Dequeue(); _pq.Dequeue();
} }
} }
[Benchmark] [Benchmark]
public void EnqueueDequeue_rcQueue() public void EnqueueDequeue_RcSortedQueue()
{ {
_rcQueue.Clear(); _sq.Clear();
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
_rcQueue.Enqueue(new Node _sq.Enqueue(new Node
{ {
id = i, id = i,
total = _priority[i], total = _priority[i],
}); });
_rcQueue.Dequeue(); _sq.Dequeue();
} }
} }
[Benchmark] [Benchmark]
public void EnqueueDequeue_heap() public void EnqueueDequeue_RcBinaryMinHeap()
{ {
_heap.Clear(); _bmHeap.Clear();
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
_heap.Push(new Node _bmHeap.Push(new Node
{ {
id = i, id = i,
total = _priority[i], total = _priority[i],
}); });
_heap.Pop(); _bmHeap.Pop();
} }
} }
[Benchmark] [Benchmark]
public void EnqueueDequeue_pqueue() public void EnqueueDequeue_PriorityQueue()
{ {
_pqueue.Clear(); _pq.Clear();
for (int i = 0; i < Count; i++) for (int i = 0; i < Count; i++)
{ {
var node = new Node var node = new Node
@ -233,9 +231,9 @@ public class PriorityQueueBenchmarks
id = i, id = i,
total = _priority[i], total = _priority[i],
}; };
_pqueue.Enqueue(node, node); _pq.Enqueue(node, node);
_pqueue.Dequeue(); _pq.Dequeue();
} }
} }
} }

View File

@ -0,0 +1,147 @@
using DotRecast.Core.Collections;
using NUnit.Framework;
namespace DotRecast.Core.Test;
public class RcBinaryMinHeapTest
{
private static readonly RcAtomicLong Gen = new();
private class Node
{
public readonly long Id;
public long Value;
public Node(int value)
{
Id = Gen.IncrementAndGet();
Value = value;
}
}
[Test]
public void TestPush()
{
var minHeap = new RcBinaryMinHeap<Node>((x, y) => x.Value.CompareTo(y.Value));
minHeap.Push(new Node(5));
minHeap.Push(new Node(3));
minHeap.Push(new Node(7));
minHeap.Push(new Node(2));
minHeap.Push(new Node(4));
// Push 후 힙의 속성을 검증
AssertHeapProperty(minHeap.ToArray());
}
[Test]
public void TestPop()
{
var minHeap = new RcBinaryMinHeap<Node>((x, y) => x.Value.CompareTo(y.Value));
minHeap.Push(new Node(5));
minHeap.Push(new Node(3));
minHeap.Push(new Node(7));
minHeap.Push(new Node(2));
minHeap.Push(new Node(4));
// Pop을 통해 최소 값부터 순서대로 제거하면서 검증
Assert.That(minHeap.Pop().Value, Is.EqualTo(2));
Assert.That(minHeap.Pop().Value, Is.EqualTo(3));
Assert.That(minHeap.Pop().Value, Is.EqualTo(4));
Assert.That(minHeap.Pop().Value, Is.EqualTo(5));
Assert.That(minHeap.Pop().Value, Is.EqualTo(7));
// 모든 요소를 Pop한 후에는 비어있어야 함
Assert.That(minHeap.IsEmpty(), Is.True);
}
[Test]
public void TestTop()
{
var minHeap = new RcBinaryMinHeap<Node>((x, y) => x.Value.CompareTo(y.Value));
minHeap.Push(new Node(5));
minHeap.Push(new Node(3));
minHeap.Push(new Node(7));
Assert.That(minHeap.Top().Value, Is.EqualTo(3));
AssertHeapProperty(minHeap.ToArray());
}
[Test]
public void TestModify()
{
var minHeap = new RcBinaryMinHeap<Node>((x, y) => x.Value.CompareTo(y.Value));
var node7 = new Node(7);
minHeap.Push(new Node(5));
minHeap.Push(new Node(3));
minHeap.Push(node7);
minHeap.Push(new Node(2));
minHeap.Push(new Node(4));
node7.Value = 1;
var result = minHeap.Modify(node7); // Modify value 7 to 1
var result2 = minHeap.Modify(new Node(4));
Assert.That(result, Is.EqualTo(true));
Assert.That(result2, Is.EqualTo(false));
Assert.That(minHeap.Top().Value, Is.EqualTo(1));
AssertHeapProperty(minHeap.ToArray());
}
[Test]
public void TestCount()
{
var minHeap = new RcBinaryMinHeap<Node>((x, y) => x.Value.CompareTo(y.Value));
minHeap.Push(new Node(5));
minHeap.Push(new Node(3));
minHeap.Push(new Node(7));
Assert.That(minHeap.Count, Is.EqualTo(3));
minHeap.Pop();
Assert.That(minHeap.Count, Is.EqualTo(2));
minHeap.Clear();
Assert.That(minHeap.Count, Is.EqualTo(0));
}
[Test]
public void TestIsEmpty()
{
var minHeap = new RcBinaryMinHeap<Node>((x, y) => x.Value.CompareTo(y.Value));
Assert.That(minHeap.IsEmpty(), Is.True);
minHeap.Push(new Node(5));
Assert.That(minHeap.IsEmpty(), Is.False);
minHeap.Pop();
Assert.That(minHeap.IsEmpty(), Is.True);
}
private void AssertHeapProperty(Node[] array)
{
for (int i = 0; i < array.Length / 2; i++)
{
int leftChildIndex = 2 * i + 1;
int rightChildIndex = 2 * i + 2;
// 왼쪽 자식 노드가 있는지 확인하고 비교
if (leftChildIndex < array.Length)
Assert.That(array[i].Value, Is.LessThanOrEqualTo(array[leftChildIndex].Value));
// 오른쪽 자식 노드가 있는지 확인하고 비교
if (rightChildIndex < array.Length)
Assert.That(array[i].Value, Is.LessThanOrEqualTo(array[rightChildIndex].Value));
}
}
}