From 1670553e529a7357276d53b0044ce7e7061de96f Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Tue, 9 Feb 2021 20:16:59 +0000 Subject: [PATCH] add binary heap implementation with support for siftdown optimization --- PriorityQueue.Benchmarks/HeapBenchmarks.cs | 161 +++--- PriorityQueue.Tests/PriorityQueueTests.cs | 8 +- PriorityQueue.Tests/PriorityQueue_Binary.cs | 104 ++++ .../PriorityQueue_ComparableTests.cs | 8 +- .../PriorityQueue_InlineComparerTests.cs | 8 +- PriorityQueue.Tests/PrioritySetTests.cs | 8 +- PriorityQueue/PriorityQueue.cs | 45 ++ PriorityQueue/PriorityQueue.csproj | 1 + PriorityQueue/PriorityQueue_Binary.cs | 531 ++++++++++++++++++ 9 files changed, 790 insertions(+), 84 deletions(-) create mode 100644 PriorityQueue.Tests/PriorityQueue_Binary.cs create mode 100644 PriorityQueue/PriorityQueue_Binary.cs diff --git a/PriorityQueue.Benchmarks/HeapBenchmarks.cs b/PriorityQueue.Benchmarks/HeapBenchmarks.cs index 8dff7a0..dfa5f22 100644 --- a/PriorityQueue.Benchmarks/HeapBenchmarks.cs +++ b/PriorityQueue.Benchmarks/HeapBenchmarks.cs @@ -15,6 +15,7 @@ public class HeapBenchmarks private int[] _priorities; private PriorityQueue _priorityQueue2; private PriorityQueue _priorityQueue; + private PriorityQueue_Binary _priorityQueueBinary; private PriorityQueue_Comparable _priorityQueueComparable; private PriorityQueue_InlineComparer _priorityQueueInlineComparer; private PrioritySet _prioritySet; @@ -30,6 +31,7 @@ public void Initialize() _priorities[i] = random.Next(); } + _priorityQueueBinary = new PriorityQueue_Binary(initialCapacity: Size); _priorityQueueComparable = new PriorityQueue_Comparable(initialCapacity: Size); _priorityQueueInlineComparer = new PriorityQueue_InlineComparer(initialCapacity: Size); _priorityQueue2 = new PriorityQueue(initialCapacity: Size); @@ -61,6 +63,29 @@ public void PriorityQueue() } } + [Benchmark] + public void PriorityQueue_Binary() + { + var queue = _priorityQueueBinary; + var priorities = _priorities; + + for (int i = 0; i < Size; i++) + { + queue.Enqueue(i, priorities[i]); + } + + for (int i = Size; i < 2 * Size; i++) + { + queue.Dequeue(); + queue.Enqueue(i, priorities[i]); + } + + while (queue.Count > 0) + { + queue.Dequeue(); + } + } + [Benchmark] public void PriorityQueue_InlineComparer() { @@ -107,73 +132,73 @@ public void PriorityQueue_Comparable() } } - //[Benchmark] - //public void PriorityQueue2() - //{ - // var queue = _priorityQueue2; - // var priorities = _priorities; - - // for (int i = 0; i < Size; i++) - // { - // queue.Enqueue(priorities[i]); - // } - - // for (int i = Size; i < 2 * Size; i++) - // { - // queue.Dequeue(); - // queue.Enqueue(priorities[i]); - // } - - // while (queue.Count > 0) - // { - // queue.Dequeue(); - // } - //} - - //[Benchmark] - //public void PrioritySet() - //{ - // var queue = _prioritySet; - // var priorities = _priorities; - - // for (int i = 0; i < Size; i++) - // { - // queue.Enqueue(i, priorities[i]); - // } - - // for (int i = Size; i < 2 * Size; i++) - // { - // queue.Dequeue(); - // queue.Enqueue(i, priorities[i]); - // } - - // while (queue.Count > 0) - // { - // queue.Dequeue(); - // } - //} - - //[Benchmark] - //public void PairingHeap() - //{ - // var heap = _pairingHeap; - // var priorities = _priorities; - - // for (int i = 0; i < Size; i++) - // { - // heap.Add(priorities[i], i); - // } - - // for (int i = Size; i < 2 * Size; i++) - // { - // heap.Pop(); - // heap.Add(priorities[i], i); - // } - - // while (heap.Count > 0) - // { - // heap.Pop(); - // } - //} + [Benchmark] + public void PriorityQueue2() + { + var queue = _priorityQueue2; + var priorities = _priorities; + + for (int i = 0; i < Size; i++) + { + queue.Enqueue(priorities[i]); + } + + for (int i = Size; i < 2 * Size; i++) + { + queue.Dequeue(); + queue.Enqueue(priorities[i]); + } + + while (queue.Count > 0) + { + queue.Dequeue(); + } + } + + [Benchmark] + public void PrioritySet() + { + var queue = _prioritySet; + var priorities = _priorities; + + for (int i = 0; i < Size; i++) + { + queue.Enqueue(i, priorities[i]); + } + + for (int i = Size; i < 2 * Size; i++) + { + queue.Dequeue(); + queue.Enqueue(i, priorities[i]); + } + + while (queue.Count > 0) + { + queue.Dequeue(); + } + } + + [Benchmark] + public void PairingHeap() + { + var heap = _pairingHeap; + var priorities = _priorities; + + for (int i = 0; i < Size; i++) + { + heap.Add(priorities[i], i); + } + + for (int i = Size; i < 2 * Size; i++) + { + heap.Pop(); + heap.Add(priorities[i], i); + } + + while (heap.Count > 0) + { + heap.Pop(); + } + } } } diff --git a/PriorityQueue.Tests/PriorityQueueTests.cs b/PriorityQueue.Tests/PriorityQueueTests.cs index 3876383..af5a10d 100644 --- a/PriorityQueue.Tests/PriorityQueueTests.cs +++ b/PriorityQueue.Tests/PriorityQueueTests.cs @@ -16,7 +16,7 @@ public static void Simple_Priority_Queue() pq.Enqueue("John", 1940); pq.Enqueue("Paul", 1942); pq.Enqueue("George", 1943); - pq.Enqueue("Ringo", 1940); + pq.Enqueue("Ringo", 1941); Assert.Equal("John", pq.Dequeue()); Assert.Equal("Ringo", pq.Dequeue()); @@ -27,7 +27,7 @@ public static void Simple_Priority_Queue() [Fact] public static void Simple_Priority_Queue_Heapify() { - var pq = new PriorityQueue(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1940) }); + var pq = new PriorityQueue(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }); Assert.Equal("John", pq.Dequeue()); Assert.Equal("Ringo", pq.Dequeue()); @@ -38,9 +38,9 @@ public static void Simple_Priority_Queue_Heapify() [Fact] public static void Simple_Priority_Queue_Enumeration() { - var pq = new PriorityQueue(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1940) }); + var pq = new PriorityQueue(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }); - (string, int)[] expected = new[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1940) }; + (string, int)[] expected = new[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }; Assert.Equal(expected, pq.UnorderedItems.ToArray()); } diff --git a/PriorityQueue.Tests/PriorityQueue_Binary.cs b/PriorityQueue.Tests/PriorityQueue_Binary.cs new file mode 100644 index 0000000..11001a2 --- /dev/null +++ b/PriorityQueue.Tests/PriorityQueue_Binary.cs @@ -0,0 +1,104 @@ +using System; +using Xunit; +using FsCheck.Xunit; +using System.Collections.Generic; +using System.Linq; + +namespace PriorityQueue.Tests +{ + public class PriorityQueue_BinaryTests + { + [Fact] + public static void Simple_Priority_Queue() + { + var pq = new PriorityQueue_Binary(); + + pq.Enqueue("John", 1940); + pq.Enqueue("Paul", 1942); + pq.Enqueue("George", 1943); + pq.Enqueue("Ringo", 1941); + + Assert.Equal("John", pq.Dequeue()); + Assert.Equal("Ringo", pq.Dequeue()); + Assert.Equal("Paul", pq.Dequeue()); + Assert.Equal("George", pq.Dequeue()); + } + + [Fact] + public static void Simple_Priority_Queue_Heapify() + { + var pq = new PriorityQueue_Binary(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }); + + Assert.Equal("John", pq.Dequeue()); + Assert.Equal("Ringo", pq.Dequeue()); + Assert.Equal("Paul", pq.Dequeue()); + Assert.Equal("George", pq.Dequeue()); + } + + [Fact] + public static void Simple_Priority_Queue_Enumeration() + { + var pq = new PriorityQueue_Binary(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }); + + (string, int)[] expected = new[] { ("John", 1940), ("Ringo", 1941), ("George", 1943), ("Paul", 1942) }; + Assert.Equal(expected, pq.UnorderedItems.ToArray()); + } + + [Property(MaxTest = 10_000)] + public static void HeapSort_Should_Work(string[] inputs) + { + string[] expected = inputs.OrderBy(inp => inp).ToArray(); + string[] actual = HeapSort(inputs).ToArray(); + Assert.Equal(expected, actual); + + static IEnumerable HeapSort(string[] inputs) + { + var pq = new PriorityQueue_Binary(); + ValidateState(pq); + + foreach (string input in inputs) + { + pq.Enqueue(input, input); + ValidateState(pq); + } + + Assert.Equal(inputs.Length, pq.Count); + + while (pq.Count > 0) + { + yield return pq.Dequeue(); + ValidateState(pq); + } + } + } + + [Property(MaxTest = 10_000)] + public static void HeapSort_Ctor_Should_Work(string[] inputs) + { + string[] expected = inputs.OrderBy(inp => inp).ToArray(); + string[] actual = HeapSort(inputs).ToArray(); + Assert.Equal(expected, actual); + + static IEnumerable HeapSort(string[] inputs) + { + var pq = new PriorityQueue_Binary(inputs.Select(x => (x, x))); + + ValidateState(pq); + Assert.Equal(inputs.Length, pq.Count); + + while (pq.Count > 0) + { + yield return pq.Dequeue(); + ValidateState(pq); + } + } + } + + private static void ValidateState(PriorityQueue_Binary pq) + { +#if DEBUG + pq.ValidateInternalState(); +#endif + } + } +} diff --git a/PriorityQueue.Tests/PriorityQueue_ComparableTests.cs b/PriorityQueue.Tests/PriorityQueue_ComparableTests.cs index 4134f58..056bd93 100644 --- a/PriorityQueue.Tests/PriorityQueue_ComparableTests.cs +++ b/PriorityQueue.Tests/PriorityQueue_ComparableTests.cs @@ -17,7 +17,7 @@ public static void Simple_Priority_Queue() pq.Enqueue("John", 1940); pq.Enqueue("Paul", 1942); pq.Enqueue("George", 1943); - pq.Enqueue("Ringo", 1940); + pq.Enqueue("Ringo", 1941); Assert.Equal("John", pq.Dequeue()); Assert.Equal("Ringo", pq.Dequeue()); @@ -28,7 +28,7 @@ public static void Simple_Priority_Queue() [Fact] public static void Simple_Priority_Queue_Heapify() { - var pq = new PriorityQueue_Comparable(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1940) }); + var pq = new PriorityQueue_Comparable(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }); Assert.Equal("John", pq.Dequeue()); Assert.Equal("Ringo", pq.Dequeue()); @@ -39,9 +39,9 @@ public static void Simple_Priority_Queue_Heapify() [Fact] public static void Simple_Priority_Queue_Enumeration() { - var pq = new PriorityQueue_Comparable(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1940) }); + var pq = new PriorityQueue_Comparable(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }); - (string, int)[] expected = new[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1940) }; + (string, int)[] expected = new[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }; Assert.Equal(expected, pq.UnorderedItems.ToArray()); } diff --git a/PriorityQueue.Tests/PriorityQueue_InlineComparerTests.cs b/PriorityQueue.Tests/PriorityQueue_InlineComparerTests.cs index e5623ae..93bb258 100644 --- a/PriorityQueue.Tests/PriorityQueue_InlineComparerTests.cs +++ b/PriorityQueue.Tests/PriorityQueue_InlineComparerTests.cs @@ -16,7 +16,7 @@ public static void Simple_Priority_Queue() pq.Enqueue("John", 1940); pq.Enqueue("Paul", 1942); pq.Enqueue("George", 1943); - pq.Enqueue("Ringo", 1940); + pq.Enqueue("Ringo", 1941); Assert.Equal("John", pq.Dequeue()); Assert.Equal("Ringo", pq.Dequeue()); @@ -27,7 +27,7 @@ public static void Simple_Priority_Queue() [Fact] public static void Simple_Priority_Queue_Heapify() { - var pq = new PriorityQueue_InlineComparer(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1940) }); + var pq = new PriorityQueue_InlineComparer(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }); Assert.Equal("John", pq.Dequeue()); Assert.Equal("Ringo", pq.Dequeue()); @@ -38,9 +38,9 @@ public static void Simple_Priority_Queue_Heapify() [Fact] public static void Simple_Priority_Queue_Enumeration() { - var pq = new PriorityQueue_InlineComparer(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1940) }); + var pq = new PriorityQueue_InlineComparer(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }); - (string, int)[] expected = new[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1940) }; + (string, int)[] expected = new[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }; Assert.Equal(expected, pq.UnorderedItems.ToArray()); } diff --git a/PriorityQueue.Tests/PrioritySetTests.cs b/PriorityQueue.Tests/PrioritySetTests.cs index c97215a..d05937c 100644 --- a/PriorityQueue.Tests/PrioritySetTests.cs +++ b/PriorityQueue.Tests/PrioritySetTests.cs @@ -17,7 +17,7 @@ public static void Simple_Priority_Queue() pq.Enqueue("John", 1940); pq.Enqueue("Paul", 1942); pq.Enqueue("George", 1943); - pq.Enqueue("Ringo", 1940); + pq.Enqueue("Ringo", 1941); Assert.Equal("John", pq.Dequeue()); Assert.Equal("Ringo", pq.Dequeue()); @@ -28,7 +28,7 @@ public static void Simple_Priority_Queue() [Fact] public static void Simple_Priority_Queue_Heapify() { - var pq = new PrioritySet(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1940) }); + var pq = new PrioritySet(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }); Assert.Equal("John", pq.Dequeue()); Assert.Equal("Ringo", pq.Dequeue()); @@ -39,9 +39,9 @@ public static void Simple_Priority_Queue_Heapify() [Fact] public static void Simple_Priority_Queue_Enumeration() { - var pq = new PrioritySet(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1940) }); + var pq = new PrioritySet(new (string, int)[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }); - (string, int)[] expected = new[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1940) }; + (string, int)[] expected = new[] { ("John", 1940), ("Paul", 1942), ("George", 1943), ("Ringo", 1941) }; Assert.Equal(expected, pq.ToArray()); } diff --git a/PriorityQueue/PriorityQueue.cs b/PriorityQueue/PriorityQueue.cs index 6edb48e..5ca1e6b 100644 --- a/PriorityQueue/PriorityQueue.cs +++ b/PriorityQueue/PriorityQueue.cs @@ -174,7 +174,11 @@ public TElement EnqueueDequeue(TElement element, TPriority priority) _version++; TElement minElement = minEntry.Element; +#if SIFTDOWN_EMPTY_NODES + SiftDownHeapPropertyRequired(index: 0, in element, in priority); +#else SiftDown(index: 0, in element, in priority); +#endif return minElement; } @@ -360,7 +364,11 @@ private void RemoveIndex(int index, out TElement element, out TPriority priority if (lastElementPos > 0) { +#if SIFTDOWN_EMPTY_NODES + SiftDownHeapPropertyRequired(index, in lastElement.Element, in lastElement.Priority); +#else SiftDown(index, in lastElement.Element, in lastElement.Priority); +#endif } //if (RuntimeHelpers.IsReferenceOrContainsReferences()) @@ -429,6 +437,43 @@ private void SiftDown(int index, in TElement element, in TPriority priority) entry.Priority = priority; } +#if SIFTDOWN_EMPTY_NODES + private void SiftDownHeapPropertyRequired(int index, in TElement element, in TPriority priority) + { + int emptyNodeIndex = SiftDownEmptyNode(index); + SiftUp(emptyNodeIndex, in element, in priority); + } + + private int SiftDownEmptyNode(int emptyNodeIndex) + { + int count = _count; + int minChildIndex; + HeapEntry[] heap = _heap; + + while ((minChildIndex = (emptyNodeIndex << 2) + 1) < count) + { + // find the child with the minimal priority + ref HeapEntry minChild = ref heap[minChildIndex]; + int childUpperBound = Math.Min(count, minChildIndex + 4); + + for (int nextChildIndex = minChildIndex + 1; nextChildIndex < childUpperBound; nextChildIndex++) + { + ref HeapEntry nextChild = ref heap[nextChildIndex]; + if (_priorityComparer.Compare(nextChild.Priority, minChild.Priority) < 0) + { + minChildIndex = nextChildIndex; + minChild = ref nextChild; + } + } + + heap[emptyNodeIndex] = minChild; + emptyNodeIndex = minChildIndex; + } + + return emptyNodeIndex; + } +#endif + private void Resize(ref HeapEntry[] heap) { int newSize = heap.Length == 0 ? DefaultCapacity : 2 * heap.Length; diff --git a/PriorityQueue/PriorityQueue.csproj b/PriorityQueue/PriorityQueue.csproj index b3f5141..fc8d6cb 100644 --- a/PriorityQueue/PriorityQueue.csproj +++ b/PriorityQueue/PriorityQueue.csproj @@ -4,6 +4,7 @@ net5.0 enable 9.0 + $(DefineConstants),SIFTDOWN_EMPTY_NODES diff --git a/PriorityQueue/PriorityQueue_Binary.cs b/PriorityQueue/PriorityQueue_Binary.cs new file mode 100644 index 0000000..dd346c4 --- /dev/null +++ b/PriorityQueue/PriorityQueue_Binary.cs @@ -0,0 +1,531 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace PriorityQueue +{ + public class PriorityQueue_Binary + { + private const int DefaultCapacity = 4; + + private readonly IComparer _priorityComparer; + + private HeapEntry[] _heap; + private int _count; + private int _version; + + private UnorderedItemsCollection? _unorderedItemsCollection; + + #region Constructors + public PriorityQueue_Binary() : this(0, null) + { + + } + + public PriorityQueue_Binary(int initialCapacity) : this(initialCapacity, null) + { + + } + + public PriorityQueue_Binary(IComparer? comparer) : this(0, comparer) + { + + } + + public PriorityQueue_Binary(int initialCapacity, IComparer? comparer) + { + if (initialCapacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(initialCapacity)); + } + + if (initialCapacity == 0) + { + _heap = Array.Empty(); + } + else + { + _heap = new HeapEntry[initialCapacity]; + } + + _priorityComparer = comparer ?? Comparer.Default; + } + + public PriorityQueue_Binary(IEnumerable<(TElement Element, TPriority Priority)> values) : this(values, null) + { + + } + + public PriorityQueue_Binary(IEnumerable<(TElement Element, TPriority Priority)> values, IComparer? comparer) + { + _priorityComparer = comparer ?? Comparer.Default; + _heap = Array.Empty(); + _count = 0; + + AppendRaw(values); + Heapify(); + } + #endregion + + public int Count => _count; + public IComparer Comparer => _priorityComparer; + + public void Enqueue(TElement element, TPriority priority) + { + _version++; + if (_count == _heap.Length) + { + Resize(ref _heap); + } + + SiftUp(index: _count++, in element, in priority); + } + + public void EnqueueRange(IEnumerable<(TElement Element, TPriority Priority)> values) + { + _version++; + if (_count == 0) + { + AppendRaw(values); + Heapify(); + } + else + { + foreach ((TElement element, TPriority priority) in values) + { + if (_count == _heap.Length) + { + Resize(ref _heap); + } + + SiftUp(index: _count++, in element, in priority); + } + } + } + + // TODO optimize + public void EnqueueRange(IEnumerable elements, TPriority priority) => EnqueueRange(elements.Select(e => (e, priority))); + + public TElement Peek() + { + if (_count == 0) + { + throw new InvalidOperationException(); + } + + return _heap[0].Element; + } + + public bool TryPeek([MaybeNullWhen(false)] out TElement element, [MaybeNullWhen(false)] out TPriority priority) + { + if (_count == 0) + { + element = default; + priority = default; + return false; + } + + (element, priority) = _heap[0]; + return true; + } + + public TElement Dequeue() + { + if (_count == 0) + { + throw new InvalidOperationException(); + } + + _version++; + RemoveIndex(index: 0, out TElement result, out _); + return result; + } + + public bool TryDequeue([MaybeNullWhen(false)] out TElement element, [MaybeNullWhen(false)] out TPriority priority) + { + if (_count == 0) + { + element = default; + priority = default; + return false; + } + + _version++; + RemoveIndex(index: 0, out element, out priority); + return true; + } + + public TElement EnqueueDequeue(TElement element, TPriority priority) + { + if (_count == 0) + { + return element; + } + + ref HeapEntry minEntry = ref _heap[0]; + if (_priorityComparer.Compare(priority, minEntry.Priority) <= 0) + { + return element; + } + + _version++; + TElement minElement = minEntry.Element; +#if SIFTDOWN_EMPTY_NODES + SiftDownHeapPropertyRequired(index: 0, in element, in priority); +#else + SiftDown(index: 0, in element, in priority); +#endif + return minElement; + } + + public void Clear() + { + _version++; + if (_count > 0) + { + //if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + Array.Clear(_heap, 0, _count); + } + + _count = 0; + } + } + + public void TrimExcess() + { + int count = _count; + int threshold = (int)(((double)_heap.Length) * 0.9); + if (count < threshold) + { + Array.Resize(ref _heap, count); + } + } + + public void EnsureCapacity(int capacity) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(); + } + + if (capacity > _heap.Length) + { + Array.Resize(ref _heap, capacity); + } + } + + public UnorderedItemsCollection UnorderedItems => _unorderedItemsCollection ??= new UnorderedItemsCollection(this); + + public class UnorderedItemsCollection : IReadOnlyCollection<(TElement Element, TPriority Priority)>, ICollection + { + private readonly PriorityQueue_Binary _priorityQueue; + + internal UnorderedItemsCollection(PriorityQueue_Binary priorityQueue) + { + _priorityQueue = priorityQueue; + } + + public int Count => _priorityQueue.Count; + public bool IsSynchronized => false; + public object SyncRoot => _priorityQueue; + + public Enumerator GetEnumerator() => new Enumerator(_priorityQueue); + IEnumerator<(TElement Element, TPriority Priority)> IEnumerable<(TElement Element, TPriority Priority)>.GetEnumerator() => new Enumerator(_priorityQueue); + IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_priorityQueue); + + bool ICollection.IsSynchronized => false; + object ICollection.SyncRoot => this; + void ICollection.CopyTo(Array array, int index) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (array.Rank != 1) + throw new ArgumentException("SR.Arg_RankMultiDimNotSupported", nameof(array)); + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), "SR.ArgumentOutOfRange_Index"); + + int arrayLen = array.Length; + if (arrayLen - index < _priorityQueue._count) + throw new ArgumentException("SR.Argument_InvalidOffLen"); + + int numToCopy = _priorityQueue._count; + HeapEntry[] heap = _priorityQueue._heap; + + for (int i = 0; i < numToCopy; i++) + { + ref HeapEntry entry = ref heap[i]; + array.SetValue((entry.Element, entry.Priority), index + i); + } + } + + public struct Enumerator : IEnumerator<(TElement Element, TPriority Priority)>, IEnumerator + { + private readonly PriorityQueue_Binary _queue; + private readonly int _version; + private int _index; + private (TElement Element, TPriority Priority) _current; + + internal Enumerator(PriorityQueue_Binary queue) + { + _version = queue._version; + _queue = queue; + _index = 0; + _current = default; + } + + public bool MoveNext() + { + PriorityQueue_Binary queue = _queue; + + if (queue._version == _version && _index < queue._count) + { + ref HeapEntry entry = ref queue._heap[_index]; + _current = (entry.Element, entry.Priority); + _index++; + return true; + } + + if (queue._version != _version) + { + throw new InvalidOperationException("collection was modified"); + } + + return false; + } + + public (TElement Element, TPriority Priority) Current => _current; + object IEnumerator.Current => _current; + + public void Reset() + { + if (_queue._version != _version) + { + throw new InvalidOperationException("collection was modified"); + } + + _index = 0; + _current = default; + } + + public void Dispose() + { + } + } + } + +#region Private Methods + private void Heapify() + { + HeapEntry[] heap = _heap; + + for (int i = (_count - 1) >> 1; i >= 0; i--) + { + HeapEntry entry = heap[i]; // ensure struct is copied before sifting + SiftDown(i, in entry.Element, in entry.Priority); + } + } + + private void AppendRaw(IEnumerable<(TElement Element, TPriority Priority)> values) + { + // TODO: specialize on ICollection types + var heap = _heap; + int count = _count; + + foreach ((TElement element, TPriority priority) in values) + { + if (count == heap.Length) + { + Resize(ref heap); + } + + ref HeapEntry entry = ref heap[count]; + entry.Element = element; + entry.Priority = priority; + count++; + } + + _heap = heap; + _count = count; + } + + private void RemoveIndex(int index, out TElement element, out TPriority priority) + { + Debug.Assert(index < _count); + + (element, priority) = _heap[index]; + + int lastElementPos = --_count; + ref HeapEntry lastElement = ref _heap[lastElementPos]; + if (lastElementPos > 0) + { +#if SIFTDOWN_EMPTY_NODES + SiftDownHeapPropertyRequired(index, in lastElement.Element, in lastElement.Priority); +#else + SiftDown(index, in lastElement.Element, in lastElement.Priority); +#endif + } + + //if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + lastElement = default; + } + } + + private void SiftUp(int index, in TElement element, in TPriority priority) + { + while (index > 0) + { + int parentIndex = (index - 1) >> 1; + ref HeapEntry parent = ref _heap[parentIndex]; + + if (_priorityComparer.Compare(parent.Priority, priority) <= 0) + { + // parentPriority <= priority, heap property is satisfed + break; + } + + _heap[index] = parent; + index = parentIndex; + } + + ref HeapEntry entry = ref _heap[index]; + entry.Element = element; + entry.Priority = priority; + } + + private void SiftDown(int index, in TElement element, in TPriority priority) + { + int minChildIndex; + int count = _count; + HeapEntry[] heap = _heap; + + while ((minChildIndex = (index << 1) + 1) < count) + { + ref HeapEntry minChild = ref heap[minChildIndex]; + int rightChildIndex = minChildIndex + 1; + + if (rightChildIndex < count) + { + ref HeapEntry rightChild = ref heap[rightChildIndex]; + if (_priorityComparer.Compare(minChild.Priority, rightChild.Priority) > 0) + { + minChild = ref rightChild; + minChildIndex = rightChildIndex; + } + } + + // compare with inserted priority + if (_priorityComparer.Compare(priority, minChild.Priority) <= 0) + { + // priority <= minChild, heap property is satisfied + break; + } + + heap[index] = minChild; + index = minChildIndex; + } + + ref HeapEntry entry = ref heap[index]; + entry.Element = element; + entry.Priority = priority; + } + +#if SIFTDOWN_EMPTY_NODES + private void SiftDownHeapPropertyRequired(int index, in TElement element, in TPriority priority) + { + int emptyNodeIndex = SiftDownEmptyNode(index); + SiftUp(emptyNodeIndex, in element, in priority); + } + + private int SiftDownEmptyNode(int emptyNodeIndex) + { + int count = _count; + int minChildIndex; + HeapEntry[] heap = _heap; + + while ((minChildIndex = (emptyNodeIndex << 1) + 1) < count) + { + int rightChildIndex = minChildIndex + 1; + ref HeapEntry minChild = ref heap[minChildIndex]; + + if (rightChildIndex < count) + { + ref HeapEntry rightChild = ref heap[rightChildIndex]; + if (_priorityComparer.Compare(minChild.Priority, rightChild.Priority) > 0) + { + minChild = ref rightChild; + minChildIndex = rightChildIndex; + } + } + + heap[emptyNodeIndex] = minChild; + emptyNodeIndex = minChildIndex; + } + + return emptyNodeIndex; + } +#endif + + private void Resize(ref HeapEntry[] heap) + { + int newSize = heap.Length == 0 ? DefaultCapacity : 2 * heap.Length; + Array.Resize(ref heap, newSize); + } + + private struct HeapEntry + { + public TElement Element; + public TPriority Priority; + + public void Deconstruct(out TElement element, out TPriority priority) + { + element = Element; + priority = Priority; + } + } + +#if DEBUG + public void ValidateInternalState() + { + if (_heap.Length < _count) + { + throw new Exception("invalid elements array length"); + } + + foreach ((var element, var idx) in _heap.Select((x, i) => (x.Element, i)).Skip(_count)) + { + if (!IsDefault(element)) + { + throw new Exception($"Non-zero element '{element}' at index {idx}."); + } + } + + foreach ((var priority, var idx) in _heap.Select((x, i) => (x.Priority, i)).Skip(_count)) + { + if (!IsDefault(priority)) + { + throw new Exception($"Non-zero priority '{priority}' at index {idx}."); + } + } + + static bool IsDefault(T value) + { + T defaultVal = default; + + if (defaultVal is null) + { + return value is null; + } + + return value!.Equals(defaultVal); + } + } +#endif +#endregion + } +} \ No newline at end of file