diff --git a/src/DotRecast.Core/Collections/RcBinaryHeap.cs b/src/DotRecast.Core/Collections/RcBinaryHeap.cs deleted file mode 100644 index 5d4ff12..0000000 --- a/src/DotRecast.Core/Collections/RcBinaryHeap.cs +++ /dev/null @@ -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 - { - public int Count => _count; - public int Capacity => _values.Length; - - public T this[int index] => _values[index]; - - private T[] _values; - private int _count; - - private Comparison _comparision; - - public RcBinaryHeap(Comparison comparison) : this(8, comparison) - { - } - - public RcBinaryHeap(int capacity, Comparison 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; - } - } -} diff --git a/src/DotRecast.Core/Collections/RcBinaryMinHeap.cs b/src/DotRecast.Core/Collections/RcBinaryMinHeap.cs new file mode 100644 index 0000000..b7d9a48 --- /dev/null +++ b/src/DotRecast.Core/Collections/RcBinaryMinHeap.cs @@ -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 + { + private readonly List _items; + private readonly Comparison _comparision; + + public int Count => _items.Count; + public int Capacity => _items.Capacity; + + public RcBinaryMinHeap(Comparison comparision) + { + _items = new List(); + _comparision = comparision; + } + + public RcBinaryMinHeap(int capacity, Comparison comparison) : this(comparison) + { + if (capacity <= 0) + throw new ArgumentException("capacity must greater than zero"); + + _items = new List(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 ToList() + { + return new List(_items); + } + } +} \ No newline at end of file diff --git a/test/DotRecast.Benchmark/Benchmarks/PriorityQueueBenchmarks.cs b/test/DotRecast.Benchmark/Benchmarks/PriorityQueueBenchmarks.cs index f44b8bd..93d8ab1 100644 --- a/test/DotRecast.Benchmark/Benchmarks/PriorityQueueBenchmarks.cs +++ b/test/DotRecast.Benchmark/Benchmarks/PriorityQueueBenchmarks.cs @@ -52,9 +52,9 @@ public class PriorityQueueBenchmarks { [Params(10, 100, 1000, 10000)] public int Count; - private RcSortedQueue _rcQueue; - private RcBinaryHeap _heap; - private PriorityQueue _pqueue; + private RcSortedQueue _sq; + private RcBinaryMinHeap _bmHeap; + private PriorityQueue _pq; private float[] _priority; @@ -75,26 +75,24 @@ public class PriorityQueueBenchmarks return x.id.CompareTo(y.id); } - _rcQueue = new(Comp); - _heap = new(Count, Comp); - _pqueue = new(Count, Comparer.Create(Comp)); + _sq = new(Comp); + _bmHeap = new(Count, Comp); + _pq = new(Count, Comparer.Create(Comp)); _priority = new float[Count]; for (int i = 0; i < Count; i++) { _priority[i] = (float)Random.Shared.NextDouble() * 100f; } - - Console.WriteLine("111"); } [Benchmark] - public void Enqueue_rcQueue() + public void Enqueue_RcSortedQueue() { - _rcQueue.Clear(); + _sq.Clear(); for (int i = 0; i < Count; i++) { - _rcQueue.Enqueue(new Node + _sq.Enqueue(new Node { id = i, total = _priority[i], @@ -103,12 +101,12 @@ public class PriorityQueueBenchmarks } [Benchmark] - public void Enqueue_heap() + public void Enqueue_RcBinaryMinHeap() { - _heap.Clear(); + _bmHeap.Clear(); for (int i = 0; i < Count; i++) { - _heap.Push(new Node + _bmHeap.Push(new Node { id = i, total = _priority[i], @@ -117,9 +115,9 @@ public class PriorityQueueBenchmarks } [Benchmark] - public void Enqueue_pqueue() + public void Enqueue_PriorityQueue() { - _pqueue.Clear(); + _pq.Clear(); for (int i = 0; i < Count; i++) { var node = new Node @@ -127,52 +125,52 @@ public class PriorityQueueBenchmarks id = i, total = _priority[i], }; - _pqueue.Enqueue(node, node); + _pq.Enqueue(node, node); } } [Benchmark] - public void DequeueAll_rcQueue() + public void DequeueAll_RcSortedQueue() { - _rcQueue.Clear(); + _sq.Clear(); for (int i = 0; i < Count; i++) { - _rcQueue.Enqueue(new Node + _sq.Enqueue(new Node { id = i, total = _priority[i], }); } - while (_rcQueue.Count() > 0) + while (_sq.Count() > 0) { - _rcQueue.Dequeue(); + _sq.Dequeue(); } } [Benchmark] - public void DequeueAll_heap() + public void DequeueAll_RcBinaryMinHeap() { - _heap.Clear(); + _bmHeap.Clear(); for (int i = 0; i < Count; i++) { - _heap.Push(new Node + _bmHeap.Push(new Node { id = i, total = _priority[i], }); } - while (_heap.Count > 0) + while (_bmHeap.Count > 0) { - _heap.Pop(); + _bmHeap.Pop(); } } [Benchmark] - public void DequeueAll_pqueue() + public void DequeueAll_PriorityQueue() { - _pqueue.Clear(); + _pq.Clear(); for (int i = 0; i < Count; i++) { var node = new Node @@ -180,52 +178,52 @@ public class PriorityQueueBenchmarks id = 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] - public void EnqueueDequeue_rcQueue() + public void EnqueueDequeue_RcSortedQueue() { - _rcQueue.Clear(); + _sq.Clear(); for (int i = 0; i < Count; i++) { - _rcQueue.Enqueue(new Node + _sq.Enqueue(new Node { id = i, total = _priority[i], }); - _rcQueue.Dequeue(); + _sq.Dequeue(); } } [Benchmark] - public void EnqueueDequeue_heap() + public void EnqueueDequeue_RcBinaryMinHeap() { - _heap.Clear(); + _bmHeap.Clear(); for (int i = 0; i < Count; i++) { - _heap.Push(new Node + _bmHeap.Push(new Node { id = i, total = _priority[i], }); - _heap.Pop(); + _bmHeap.Pop(); } } [Benchmark] - public void EnqueueDequeue_pqueue() + public void EnqueueDequeue_PriorityQueue() { - _pqueue.Clear(); + _pq.Clear(); for (int i = 0; i < Count; i++) { var node = new Node @@ -233,9 +231,9 @@ public class PriorityQueueBenchmarks id = i, total = _priority[i], }; - _pqueue.Enqueue(node, node); + _pq.Enqueue(node, node); - _pqueue.Dequeue(); + _pq.Dequeue(); } } } \ No newline at end of file diff --git a/test/DotRecast.Core.Test/RcBinaryMinHeapTest.cs b/test/DotRecast.Core.Test/RcBinaryMinHeapTest.cs new file mode 100644 index 0000000..7dcfc2f --- /dev/null +++ b/test/DotRecast.Core.Test/RcBinaryMinHeapTest.cs @@ -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((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((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((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((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((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((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)); + } + } +} \ No newline at end of file