999 lines
40 KiB
C#
999 lines
40 KiB
C#
#nullable enable
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace System.Collections.Generic
|
|
{
|
|
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
|
|
// ported from:
|
|
// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs
|
|
internal sealed class PriorityQueueDebugView<TElement, TPriority>
|
|
{
|
|
private readonly PriorityQueue<TElement, TPriority> _queue;
|
|
private readonly bool _sort;
|
|
|
|
public PriorityQueueDebugView(PriorityQueue<TElement, TPriority> queue)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(queue);
|
|
|
|
_queue = queue;
|
|
_sort = true;
|
|
}
|
|
|
|
public PriorityQueueDebugView(PriorityQueue<TElement, TPriority>.UnorderedItemsCollection collection)
|
|
{
|
|
_queue = collection?._queue ?? throw new System.ArgumentNullException(nameof(collection));
|
|
}
|
|
|
|
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
|
public (TElement Element, TPriority Priority)[] Items
|
|
{
|
|
get
|
|
{
|
|
List<(TElement Element, TPriority Priority)> list = new(_queue.UnorderedItems);
|
|
if (_sort) list.Sort((i1, i2) => _queue.Comparer.Compare(i1.Priority, i2.Priority));
|
|
|
|
return list.ToArray();
|
|
}
|
|
}
|
|
}
|
|
|
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
|
internal static class SR
|
|
{
|
|
internal const string ArgumentOutOfRange_NeedNonNegNum = "Non-negative number required.";
|
|
internal const string ArgumentOutOfRange_IndexMustBeLessOrEqual = "Index must be less or equal";
|
|
internal const string InvalidOperation_EmptyQueue = "The queue is empty.";
|
|
internal const string InvalidOperation_EnumFailedVersion = "Collection modified while iterating over it.";
|
|
internal const string Arg_NonZeroLowerBound = "Non-zero lower bound required.";
|
|
internal const string Arg_RankMultiDimNotSupported = "Multi-dimensional arrays not supported.";
|
|
internal const string Argument_InvalidArrayType = "Invalid array type.";
|
|
internal const string Argument_InvalidOffLen = "Invalid offset or length.";
|
|
}
|
|
|
|
internal static class ArgumentNullException
|
|
{
|
|
public static void ThrowIfNull(object o)
|
|
{
|
|
if (o == null)
|
|
throw new System.ArgumentNullException(); // hard to do it differently without C# 10's features
|
|
}
|
|
}
|
|
|
|
internal static class ArrayEx
|
|
{
|
|
internal const int MaxLength = int.MaxValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal helper functions for working with enumerables.
|
|
/// </summary>
|
|
internal static class EnumerableHelpers
|
|
{
|
|
/// <summary>Converts an enumerable to an array using the same logic as List{T}.</summary>
|
|
/// <param name="source">The enumerable to convert.</param>
|
|
/// <param name="length">The number of items stored in the resulting array, 0-indexed.</param>
|
|
/// <returns>
|
|
/// The resulting array. The length of the array may be greater than <paramref name="length" />,
|
|
/// which is the actual number of elements in the array.
|
|
/// </returns>
|
|
internal static T[] ToArray<T>(IEnumerable<T> source, out int length)
|
|
{
|
|
if (source is ICollection<T> ic)
|
|
{
|
|
int count = ic.Count;
|
|
if (count != 0)
|
|
{
|
|
// Allocate an array of the desired size, then copy the elements into it. Note that this has the same
|
|
// issue regarding concurrency as other existing collections like List<T>. If the collection size
|
|
// concurrently changes between the array allocation and the CopyTo, we could end up either getting an
|
|
// exception from overrunning the array (if the size went up) or we could end up not filling as many
|
|
// items as 'count' suggests (if the size went down). This is only an issue for concurrent collections
|
|
// that implement ICollection<T>, which as of .NET 4.6 is just ConcurrentDictionary<TKey, TValue>.
|
|
var arr = new T[count];
|
|
ic.CopyTo(arr, 0);
|
|
length = count;
|
|
return arr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
using (var en = source.GetEnumerator())
|
|
{
|
|
if (en.MoveNext())
|
|
{
|
|
const int DefaultCapacity = 4;
|
|
var arr = new T[DefaultCapacity];
|
|
arr[0] = en.Current;
|
|
int count = 1;
|
|
|
|
while (en.MoveNext())
|
|
{
|
|
if (count == arr.Length)
|
|
{
|
|
// This is the same growth logic as in List<T>:
|
|
// If the array is currently empty, we make it a default size. Otherwise, we attempt to
|
|
// double the size of the array. Doubling will overflow once the size of the array reaches
|
|
// 2^30, since doubling to 2^31 is 1 larger than Int32.MaxValue. In that case, we instead
|
|
// constrain the length to be Array.MaxLength (this overflow check works because of the
|
|
// cast to uint).
|
|
int newLength = count << 1;
|
|
if ((uint)newLength > ArrayEx.MaxLength)
|
|
newLength = ArrayEx.MaxLength <= count ? count + 1 : ArrayEx.MaxLength;
|
|
|
|
Array.Resize(ref arr, newLength);
|
|
}
|
|
|
|
arr[count++] = en.Current;
|
|
}
|
|
|
|
length = count;
|
|
return arr;
|
|
}
|
|
}
|
|
}
|
|
|
|
length = 0;
|
|
return Array.Empty<T>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a min priority queue.
|
|
/// </summary>
|
|
/// <typeparam name="TElement">Specifies the type of elements in the queue.</typeparam>
|
|
/// <typeparam name="TPriority">Specifies the type of priority associated with enqueued elements.</typeparam>
|
|
/// <remarks>
|
|
/// Implements an array-backed quaternary min-heap. Each element is enqueued with an associated priority
|
|
/// that determines the dequeue order: elements with the lowest priority get dequeued first.
|
|
/// </remarks>
|
|
[DebuggerDisplay("Count = {Count}")]
|
|
[DebuggerTypeProxy(typeof(PriorityQueueDebugView<,>))]
|
|
public class PriorityQueue<TElement, TPriority>
|
|
{
|
|
/// <summary>
|
|
/// Specifies the arity of the d-ary heap, which here is quaternary.
|
|
/// It is assumed that this value is a power of 2.
|
|
/// </summary>
|
|
private const int Arity = 4;
|
|
|
|
/// <summary>
|
|
/// The binary logarithm of <see cref="Arity" />.
|
|
/// </summary>
|
|
private const int Log2Arity = 2;
|
|
|
|
/// <summary>
|
|
/// Custom comparer used to order the heap.
|
|
/// </summary>
|
|
private readonly IComparer<TPriority>? _comparer;
|
|
|
|
/// <summary>
|
|
/// Represents an implicit heap-ordered complete d-ary tree, stored as an array.
|
|
/// </summary>
|
|
private (TElement Element, TPriority Priority)[] _nodes;
|
|
|
|
/// <summary>
|
|
/// The number of nodes in the heap.
|
|
/// </summary>
|
|
private int _size;
|
|
|
|
/// <summary>
|
|
/// Lazily-initialized collection used to expose the contents of the queue.
|
|
/// </summary>
|
|
private UnorderedItemsCollection? _unorderedItems;
|
|
|
|
/// <summary>
|
|
/// Version updated on mutation to help validate enumerators operate on a consistent state.
|
|
/// </summary>
|
|
private int _version;
|
|
|
|
#if DEBUG
|
|
static PriorityQueue()
|
|
{
|
|
Debug.Assert(Log2Arity > 0 && Math.Pow(2, Log2Arity) == Arity);
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}" /> class.
|
|
/// </summary>
|
|
public PriorityQueue()
|
|
{
|
|
_nodes = Array.Empty<(TElement, TPriority)>();
|
|
_comparer = InitializeComparer(null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}" /> class
|
|
/// with the specified initial capacity.
|
|
/// </summary>
|
|
/// <param name="initialCapacity">Initial capacity to allocate in the underlying heap array.</param>
|
|
/// <exception cref="ArgumentOutOfRangeException">
|
|
/// The specified <paramref name="initialCapacity" /> was negative.
|
|
/// </exception>
|
|
public PriorityQueue(int initialCapacity)
|
|
: this(initialCapacity, null)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}" /> class
|
|
/// with the specified custom priority comparer.
|
|
/// </summary>
|
|
/// <param name="comparer">
|
|
/// Custom comparer dictating the ordering of elements.
|
|
/// Uses <see cref="Comparer{T}.Default" /> if the argument is <see langword="null" />.
|
|
/// </param>
|
|
public PriorityQueue(IComparer<TPriority>? comparer)
|
|
{
|
|
_nodes = Array.Empty<(TElement, TPriority)>();
|
|
_comparer = InitializeComparer(comparer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}" /> class
|
|
/// with the specified initial capacity and custom priority comparer.
|
|
/// </summary>
|
|
/// <param name="initialCapacity">Initial capacity to allocate in the underlying heap array.</param>
|
|
/// <param name="comparer">
|
|
/// Custom comparer dictating the ordering of elements.
|
|
/// Uses <see cref="Comparer{T}.Default" /> if the argument is <see langword="null" />.
|
|
/// </param>
|
|
/// <exception cref="ArgumentOutOfRangeException">
|
|
/// The specified <paramref name="initialCapacity" /> was negative.
|
|
/// </exception>
|
|
public PriorityQueue(int initialCapacity, IComparer<TPriority>? comparer)
|
|
{
|
|
if (initialCapacity < 0)
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(initialCapacity), initialCapacity, SR.ArgumentOutOfRange_NeedNonNegNum);
|
|
|
|
_nodes = new (TElement, TPriority)[initialCapacity];
|
|
_comparer = InitializeComparer(comparer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}" /> class
|
|
/// that is populated with the specified elements and priorities.
|
|
/// </summary>
|
|
/// <param name="items">The pairs of elements and priorities with which to populate the queue.</param>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// The specified <paramref name="items" /> argument was <see langword="null" />.
|
|
/// </exception>
|
|
/// <remarks>
|
|
/// Constructs the heap using a heapify operation,
|
|
/// which is generally faster than enqueuing individual elements sequentially.
|
|
/// </remarks>
|
|
public PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> items)
|
|
: this(items, null)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PriorityQueue{TElement, TPriority}" /> class
|
|
/// that is populated with the specified elements and priorities,
|
|
/// and with the specified custom priority comparer.
|
|
/// </summary>
|
|
/// <param name="items">The pairs of elements and priorities with which to populate the queue.</param>
|
|
/// <param name="comparer">
|
|
/// Custom comparer dictating the ordering of elements.
|
|
/// Uses <see cref="Comparer{T}.Default" /> if the argument is <see langword="null" />.
|
|
/// </param>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// The specified <paramref name="items" /> argument was <see langword="null" />.
|
|
/// </exception>
|
|
/// <remarks>
|
|
/// Constructs the heap using a heapify operation,
|
|
/// which is generally faster than enqueuing individual elements sequentially.
|
|
/// </remarks>
|
|
public PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> items, IComparer<TPriority>? comparer)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(items);
|
|
|
|
_nodes = EnumerableHelpers.ToArray(items, out _size);
|
|
_comparer = InitializeComparer(comparer);
|
|
|
|
if (_size > 1) Heapify();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the number of elements contained in the <see cref="PriorityQueue{TElement, TPriority}" />.
|
|
/// </summary>
|
|
public int Count => _size;
|
|
|
|
/// <summary>
|
|
/// Gets the priority comparer used by the <see cref="PriorityQueue{TElement, TPriority}" />.
|
|
/// </summary>
|
|
public IComparer<TPriority> Comparer => _comparer ?? Comparer<TPriority>.Default;
|
|
|
|
/// <summary>
|
|
/// Gets a collection that enumerates the elements of the queue in an unordered manner.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The enumeration does not order items by priority, since that would require N * log(N) time and N space.
|
|
/// Items are instead enumerated following the internal array heap layout.
|
|
/// </remarks>
|
|
public UnorderedItemsCollection UnorderedItems => _unorderedItems ??= new UnorderedItemsCollection(this);
|
|
|
|
/// <summary>
|
|
/// Adds the specified element with associated priority to the <see cref="PriorityQueue{TElement, TPriority}" />.
|
|
/// </summary>
|
|
/// <param name="element">The element to add to the <see cref="PriorityQueue{TElement, TPriority}" />.</param>
|
|
/// <param name="priority">The priority with which to associate the new element.</param>
|
|
public void Enqueue(TElement element, TPriority priority)
|
|
{
|
|
// Virtually add the node at the end of the underlying array.
|
|
// Note that the node being enqueued does not need to be physically placed
|
|
// there at this point, as such an assignment would be redundant.
|
|
|
|
int currentSize = _size++;
|
|
_version++;
|
|
|
|
if (_nodes.Length == currentSize) Grow(currentSize + 1);
|
|
|
|
if (_comparer == null)
|
|
MoveUpDefaultComparer((element, priority), currentSize);
|
|
else
|
|
MoveUpCustomComparer((element, priority), currentSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the minimal element from the <see cref="PriorityQueue{TElement, TPriority}" /> without removing it.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">The <see cref="PriorityQueue{TElement, TPriority}" /> is empty.</exception>
|
|
/// <returns>The minimal element of the <see cref="PriorityQueue{TElement, TPriority}" />.</returns>
|
|
public TElement Peek()
|
|
{
|
|
if (_size == 0) throw new InvalidOperationException(SR.InvalidOperation_EmptyQueue);
|
|
|
|
return _nodes[0].Element;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes and returns the minimal element from the <see cref="PriorityQueue{TElement, TPriority}" />.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">The queue is empty.</exception>
|
|
/// <returns>The minimal element of the <see cref="PriorityQueue{TElement, TPriority}" />.</returns>
|
|
public TElement Dequeue()
|
|
{
|
|
if (_size == 0) throw new InvalidOperationException(SR.InvalidOperation_EmptyQueue);
|
|
|
|
var element = _nodes[0].Element;
|
|
RemoveRootNode();
|
|
return element;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the minimal element from the <see cref="PriorityQueue{TElement, TPriority}" />,
|
|
/// and copies it to the <paramref name="element" /> parameter,
|
|
/// and its associated priority to the <paramref name="priority" /> parameter.
|
|
/// </summary>
|
|
/// <param name="element">The removed element.</param>
|
|
/// <param name="priority">The priority associated with the removed element.</param>
|
|
/// <returns>
|
|
/// <see langword="true" /> if the element is successfully removed;
|
|
/// <see langword="false" /> if the <see cref="PriorityQueue{TElement, TPriority}" /> is empty.
|
|
/// </returns>
|
|
public bool TryDequeue([MaybeNullWhen(false)] out TElement element,
|
|
[MaybeNullWhen(false)] out TPriority priority)
|
|
{
|
|
if (_size != 0)
|
|
{
|
|
(element, priority) = _nodes[0];
|
|
RemoveRootNode();
|
|
return true;
|
|
}
|
|
|
|
element = default;
|
|
priority = default;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value that indicates whether there is a minimal element in the
|
|
/// <see cref="PriorityQueue{TElement, TPriority}" />,
|
|
/// and if one is present, copies it to the <paramref name="element" /> parameter,
|
|
/// and its associated priority to the <paramref name="priority" /> parameter.
|
|
/// The element is not removed from the <see cref="PriorityQueue{TElement, TPriority}" />.
|
|
/// </summary>
|
|
/// <param name="element">The minimal element in the queue.</param>
|
|
/// <param name="priority">The priority associated with the minimal element.</param>
|
|
/// <returns>
|
|
/// <see langword="true" /> if there is a minimal element;
|
|
/// <see langword="false" /> if the <see cref="PriorityQueue{TElement, TPriority}" /> is empty.
|
|
/// </returns>
|
|
public bool TryPeek([MaybeNullWhen(false)] out TElement element,
|
|
[MaybeNullWhen(false)] out TPriority priority)
|
|
{
|
|
if (_size != 0)
|
|
{
|
|
(element, priority) = _nodes[0];
|
|
return true;
|
|
}
|
|
|
|
element = default;
|
|
priority = default;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the specified element with associated priority to the <see cref="PriorityQueue{TElement, TPriority}" />,
|
|
/// and immediately removes the minimal element, returning the result.
|
|
/// </summary>
|
|
/// <param name="element">The element to add to the <see cref="PriorityQueue{TElement, TPriority}" />.</param>
|
|
/// <param name="priority">The priority with which to associate the new element.</param>
|
|
/// <returns>The minimal element removed after the enqueue operation.</returns>
|
|
/// <remarks>
|
|
/// Implements an insert-then-extract heap operation that is generally more efficient
|
|
/// than sequencing Enqueue and Dequeue operations: in the worst case scenario only one
|
|
/// shift-down operation is required.
|
|
/// </remarks>
|
|
public TElement EnqueueDequeue(TElement element, TPriority priority)
|
|
{
|
|
if (_size != 0)
|
|
{
|
|
var root = _nodes[0];
|
|
|
|
if (_comparer == null)
|
|
{
|
|
if (Comparer<TPriority>.Default.Compare(priority, root.Priority) > 0)
|
|
{
|
|
MoveDownDefaultComparer((element, priority), 0);
|
|
_version++;
|
|
return root.Element;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_comparer.Compare(priority, root.Priority) > 0)
|
|
{
|
|
MoveDownCustomComparer((element, priority), 0);
|
|
_version++;
|
|
return root.Element;
|
|
}
|
|
}
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enqueues a sequence of element/priority pairs to the <see cref="PriorityQueue{TElement, TPriority}" />.
|
|
/// </summary>
|
|
/// <param name="items">The pairs of elements and priorities to add to the queue.</param>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// The specified <paramref name="items" /> argument was <see langword="null" />.
|
|
/// </exception>
|
|
public void EnqueueRange(IEnumerable<(TElement Element, TPriority Priority)> items)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(items);
|
|
|
|
int count = 0;
|
|
var collection =
|
|
items as ICollection<(TElement Element, TPriority Priority)>;
|
|
if (collection is not null && (count = collection.Count) > _nodes.Length - _size) Grow(_size + count);
|
|
|
|
if (_size == 0)
|
|
{
|
|
// build using Heapify() if the queue is empty.
|
|
|
|
if (collection is not null)
|
|
{
|
|
collection.CopyTo(_nodes, 0);
|
|
_size = count;
|
|
}
|
|
else
|
|
{
|
|
int i = 0;
|
|
(TElement, TPriority)[] nodes = _nodes;
|
|
foreach (var (element, priority) in items)
|
|
{
|
|
if (nodes.Length == i)
|
|
{
|
|
Grow(i + 1);
|
|
nodes = _nodes;
|
|
}
|
|
|
|
nodes[i++] = (element, priority);
|
|
}
|
|
|
|
_size = i;
|
|
}
|
|
|
|
_version++;
|
|
|
|
if (_size > 1) Heapify();
|
|
}
|
|
else
|
|
{
|
|
foreach (var (element, priority) in items) Enqueue(element, priority);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enqueues a sequence of elements pairs to the <see cref="PriorityQueue{TElement, TPriority}" />,
|
|
/// all associated with the specified priority.
|
|
/// </summary>
|
|
/// <param name="elements">The elements to add to the queue.</param>
|
|
/// <param name="priority">The priority to associate with the new elements.</param>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// The specified <paramref name="elements" /> argument was <see langword="null" />.
|
|
/// </exception>
|
|
public void EnqueueRange(IEnumerable<TElement> elements, TPriority priority)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(elements);
|
|
|
|
int count;
|
|
if (elements is ICollection<(TElement Element, TPriority Priority)> collection &&
|
|
(count = collection.Count) > _nodes.Length - _size)
|
|
Grow(_size + count);
|
|
|
|
if (_size == 0)
|
|
{
|
|
// build using Heapify() if the queue is empty.
|
|
|
|
int i = 0;
|
|
(TElement, TPriority)[] nodes = _nodes;
|
|
foreach (var element in elements)
|
|
{
|
|
if (nodes.Length == i)
|
|
{
|
|
Grow(i + 1);
|
|
nodes = _nodes;
|
|
}
|
|
|
|
nodes[i++] = (element, priority);
|
|
}
|
|
|
|
_size = i;
|
|
_version++;
|
|
|
|
if (i > 1) Heapify();
|
|
}
|
|
else
|
|
{
|
|
foreach (var element in elements) Enqueue(element, priority);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all items from the <see cref="PriorityQueue{TElement, TPriority}" />.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
if (RuntimeHelpers.IsReferenceOrContainsReferences<(TElement, TPriority)>())
|
|
// Clear the elements so that the gc can reclaim the references
|
|
Array.Clear(_nodes, 0, _size);
|
|
|
|
_size = 0;
|
|
_version++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures that the <see cref="PriorityQueue{TElement, TPriority}" /> can hold up to
|
|
/// <paramref name="capacity" /> items without further expansion of its backing storage.
|
|
/// </summary>
|
|
/// <param name="capacity">The minimum capacity to be used.</param>
|
|
/// <exception cref="ArgumentOutOfRangeException">
|
|
/// The specified <paramref name="capacity" /> is negative.
|
|
/// </exception>
|
|
/// <returns>The current capacity of the <see cref="PriorityQueue{TElement, TPriority}" />.</returns>
|
|
public int EnsureCapacity(int capacity)
|
|
{
|
|
if (capacity < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(capacity), capacity, SR.ArgumentOutOfRange_NeedNonNegNum);
|
|
|
|
if (_nodes.Length < capacity)
|
|
{
|
|
Grow(capacity);
|
|
_version++;
|
|
}
|
|
|
|
return _nodes.Length;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the capacity to the actual number of items in the <see cref="PriorityQueue{TElement, TPriority}" />,
|
|
/// if that is less than 90 percent of current capacity.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method can be used to minimize a collection's memory overhead
|
|
/// if no new elements will be added to the collection.
|
|
/// </remarks>
|
|
public void TrimExcess()
|
|
{
|
|
int threshold = (int)(_nodes.Length * 0.9);
|
|
if (_size < threshold)
|
|
{
|
|
Array.Resize(ref _nodes, _size);
|
|
_version++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grows the priority queue to match the specified min capacity.
|
|
/// </summary>
|
|
private void Grow(int minCapacity)
|
|
{
|
|
Debug.Assert(_nodes.Length < minCapacity);
|
|
|
|
const int GrowFactor = 2;
|
|
const int MinimumGrow = 4;
|
|
|
|
int newcapacity = GrowFactor * _nodes.Length;
|
|
|
|
// Allow the queue to grow to maximum possible capacity (~2G elements) before encountering overflow.
|
|
// Note that this check works even when _nodes.Length overflowed thanks to the (uint) cast
|
|
if ((uint)newcapacity > ArrayEx.MaxLength) newcapacity = ArrayEx.MaxLength;
|
|
|
|
// Ensure minimum growth is respected.
|
|
newcapacity = Math.Max(newcapacity, _nodes.Length + MinimumGrow);
|
|
|
|
// If the computed capacity is still less than specified, set to the original argument.
|
|
// Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize.
|
|
if (newcapacity < minCapacity) newcapacity = minCapacity;
|
|
|
|
Array.Resize(ref _nodes, newcapacity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the node from the root of the heap
|
|
/// </summary>
|
|
private void RemoveRootNode()
|
|
{
|
|
int lastNodeIndex = --_size;
|
|
_version++;
|
|
|
|
if (lastNodeIndex > 0)
|
|
{
|
|
var lastNode = _nodes[lastNodeIndex];
|
|
if (_comparer == null)
|
|
MoveDownDefaultComparer(lastNode, 0);
|
|
else
|
|
MoveDownCustomComparer(lastNode, 0);
|
|
}
|
|
|
|
if (RuntimeHelpers.IsReferenceOrContainsReferences<(TElement, TPriority)>())
|
|
_nodes[lastNodeIndex] = default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the index of an element's parent.
|
|
/// </summary>
|
|
private static int GetParentIndex(int index) => (index - 1) >> Log2Arity;
|
|
|
|
/// <summary>
|
|
/// Gets the index of the first child of an element.
|
|
/// </summary>
|
|
private static int GetFirstChildIndex(int index) => (index << Log2Arity) + 1;
|
|
|
|
/// <summary>
|
|
/// Converts an unordered list into a heap.
|
|
/// </summary>
|
|
private void Heapify()
|
|
{
|
|
// Leaves of the tree are in fact 1-element heaps, for which there
|
|
// is no need to correct them. The heap property needs to be restored
|
|
// only for higher nodes, starting from the first node that has children.
|
|
// It is the parent of the very last element in the array.
|
|
|
|
var nodes = _nodes;
|
|
int lastParentWithChildren = GetParentIndex(_size - 1);
|
|
|
|
if (_comparer == null)
|
|
for (int index = lastParentWithChildren; index >= 0; --index)
|
|
MoveDownDefaultComparer(nodes[index], index);
|
|
else
|
|
for (int index = lastParentWithChildren; index >= 0; --index)
|
|
MoveDownCustomComparer(nodes[index], index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves a node up in the tree to restore heap order.
|
|
/// </summary>
|
|
private void MoveUpDefaultComparer((TElement Element, TPriority Priority) node, int nodeIndex)
|
|
{
|
|
// Instead of swapping items all the way to the root, we will perform
|
|
// a similar optimization as in the insertion sort.
|
|
|
|
Debug.Assert(_comparer is null);
|
|
Debug.Assert(0 <= nodeIndex && nodeIndex < _size);
|
|
|
|
var nodes = _nodes;
|
|
|
|
while (nodeIndex > 0)
|
|
{
|
|
int parentIndex = GetParentIndex(nodeIndex);
|
|
var parent = nodes[parentIndex];
|
|
|
|
if (Comparer<TPriority>.Default.Compare(node.Priority, parent.Priority) < 0)
|
|
{
|
|
nodes[nodeIndex] = parent;
|
|
nodeIndex = parentIndex;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
nodes[nodeIndex] = node;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves a node up in the tree to restore heap order.
|
|
/// </summary>
|
|
private void MoveUpCustomComparer((TElement Element, TPriority Priority) node, int nodeIndex)
|
|
{
|
|
// Instead of swapping items all the way to the root, we will perform
|
|
// a similar optimization as in the insertion sort.
|
|
|
|
Debug.Assert(_comparer is not null);
|
|
Debug.Assert(0 <= nodeIndex && nodeIndex < _size);
|
|
|
|
var comparer = _comparer;
|
|
var nodes = _nodes;
|
|
|
|
while (nodeIndex > 0)
|
|
{
|
|
int parentIndex = GetParentIndex(nodeIndex);
|
|
var parent = nodes[parentIndex];
|
|
|
|
if (comparer != null && comparer.Compare(node.Priority, parent.Priority) < 0)
|
|
{
|
|
nodes[nodeIndex] = parent;
|
|
nodeIndex = parentIndex;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
nodes[nodeIndex] = node;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves a node down in the tree to restore heap order.
|
|
/// </summary>
|
|
private void MoveDownDefaultComparer((TElement Element, TPriority Priority) node, int nodeIndex)
|
|
{
|
|
// The node to move down will not actually be swapped every time.
|
|
// Rather, values on the affected path will be moved up, thus leaving a free spot
|
|
// for this value to drop in. Similar optimization as in the insertion sort.
|
|
|
|
Debug.Assert(_comparer is null);
|
|
Debug.Assert(0 <= nodeIndex && nodeIndex < _size);
|
|
|
|
var nodes = _nodes;
|
|
int size = _size;
|
|
|
|
int i;
|
|
while ((i = GetFirstChildIndex(nodeIndex)) < size)
|
|
{
|
|
// Find the child node with the minimal priority
|
|
var minChild = nodes[i];
|
|
int minChildIndex = i;
|
|
|
|
int childIndexUpperBound = Math.Min(i + Arity, size);
|
|
while (++i < childIndexUpperBound)
|
|
{
|
|
var nextChild = nodes[i];
|
|
if (Comparer<TPriority>.Default.Compare(nextChild.Priority, minChild.Priority) < 0)
|
|
{
|
|
minChild = nextChild;
|
|
minChildIndex = i;
|
|
}
|
|
}
|
|
|
|
// Heap property is satisfied; insert node in this location.
|
|
if (Comparer<TPriority>.Default.Compare(node.Priority, minChild.Priority) <= 0) break;
|
|
|
|
// Move the minimal child up by one node and
|
|
// continue recursively from its location.
|
|
nodes[nodeIndex] = minChild;
|
|
nodeIndex = minChildIndex;
|
|
}
|
|
|
|
nodes[nodeIndex] = node;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves a node down in the tree to restore heap order.
|
|
/// </summary>
|
|
private void MoveDownCustomComparer((TElement Element, TPriority Priority) node, int nodeIndex)
|
|
{
|
|
// The node to move down will not actually be swapped every time.
|
|
// Rather, values on the affected path will be moved up, thus leaving a free spot
|
|
// for this value to drop in. Similar optimization as in the insertion sort.
|
|
|
|
Debug.Assert(_comparer is not null);
|
|
Debug.Assert(0 <= nodeIndex && nodeIndex < _size);
|
|
|
|
var comparer = _comparer;
|
|
var nodes = _nodes;
|
|
int size = _size;
|
|
|
|
int i;
|
|
while ((i = GetFirstChildIndex(nodeIndex)) < size)
|
|
{
|
|
// Find the child node with the minimal priority
|
|
var minChild = nodes[i];
|
|
int minChildIndex = i;
|
|
|
|
int childIndexUpperBound = Math.Min(i + Arity, size);
|
|
while (++i < childIndexUpperBound)
|
|
{
|
|
var nextChild = nodes[i];
|
|
if (comparer != null && comparer.Compare(nextChild.Priority, minChild.Priority) < 0)
|
|
{
|
|
minChild = nextChild;
|
|
minChildIndex = i;
|
|
}
|
|
}
|
|
|
|
// Heap property is satisfied; insert node in this location.
|
|
if (comparer != null && comparer.Compare(node.Priority, minChild.Priority) <= 0) break;
|
|
|
|
// Move the minimal child up by one node and continue recursively from its location.
|
|
nodes[nodeIndex] = minChild;
|
|
nodeIndex = minChildIndex;
|
|
}
|
|
|
|
nodes[nodeIndex] = node;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the custom comparer to be used internally by the heap.
|
|
/// </summary>
|
|
private static IComparer<TPriority>? InitializeComparer(IComparer<TPriority>? comparer)
|
|
{
|
|
if (typeof(TPriority).IsValueType)
|
|
{
|
|
if (comparer == Comparer<TPriority>.Default)
|
|
// if the user manually specifies the default comparer,
|
|
// revert to using the optimized path.
|
|
return null;
|
|
|
|
return comparer;
|
|
}
|
|
|
|
// Currently the JIT doesn't optimize direct Comparer<T>.Default.Compare
|
|
// calls for reference types, so we want to cache the comparer instance instead.
|
|
// TODO https://github.com/dotnet/runtime/issues/10050: Update if this changes in the future.
|
|
return comparer ?? Comparer<TPriority>.Default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerates the contents of a <see cref="PriorityQueue{TElement, TPriority}" />, without any ordering guarantees.
|
|
/// </summary>
|
|
[DebuggerDisplay("Count = {Count}")]
|
|
[DebuggerTypeProxy(typeof(PriorityQueueDebugView<,>))]
|
|
public sealed class UnorderedItemsCollection : IReadOnlyCollection<(TElement Element, TPriority Priority)>,
|
|
ICollection
|
|
{
|
|
internal readonly PriorityQueue<TElement, TPriority> _queue;
|
|
|
|
internal UnorderedItemsCollection(PriorityQueue<TElement, TPriority> queue)
|
|
{
|
|
_queue = queue;
|
|
}
|
|
|
|
object ICollection.SyncRoot => this;
|
|
bool ICollection.IsSynchronized => false;
|
|
|
|
void ICollection.CopyTo(Array array, int index)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(array);
|
|
|
|
if (array.Rank != 1) throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
|
|
|
|
if (array.GetLowerBound(0) != 0) throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array));
|
|
|
|
if (index < 0 || index > array.Length)
|
|
throw new ArgumentOutOfRangeException(nameof(index), index,
|
|
SR.ArgumentOutOfRange_IndexMustBeLessOrEqual);
|
|
|
|
if (array.Length - index < _queue._size) throw new ArgumentException(SR.Argument_InvalidOffLen);
|
|
|
|
try
|
|
{
|
|
Array.Copy(_queue._nodes, 0, array, index, _queue._size);
|
|
}
|
|
catch (ArrayTypeMismatchException)
|
|
{
|
|
throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array));
|
|
}
|
|
}
|
|
|
|
public int Count => _queue._size;
|
|
|
|
IEnumerator<(TElement Element, TPriority Priority)> IEnumerable<(TElement Element, TPriority Priority)>.
|
|
GetEnumerator() => GetEnumerator();
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
|
|
/// <summary>
|
|
/// Returns an enumerator that iterates through the <see cref="UnorderedItems" />.
|
|
/// </summary>
|
|
/// <returns>An <see cref="Enumerator" /> for the <see cref="UnorderedItems" />.</returns>
|
|
public Enumerator GetEnumerator() => new(_queue);
|
|
|
|
/// <summary>
|
|
/// Enumerates the element and priority pairs of a <see cref="PriorityQueue{TElement, TPriority}" />,
|
|
/// without any ordering guarantees.
|
|
/// </summary>
|
|
public struct Enumerator : IEnumerator<(TElement Element, TPriority Priority)>
|
|
{
|
|
private readonly PriorityQueue<TElement, TPriority> _queue;
|
|
private readonly int _version;
|
|
private int _index;
|
|
|
|
internal Enumerator(PriorityQueue<TElement, TPriority> queue)
|
|
{
|
|
_queue = queue;
|
|
_index = 0;
|
|
_version = queue._version;
|
|
Current = default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases all resources used by the <see cref="Enumerator" />.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Advances the enumerator to the next element of the <see cref="UnorderedItems" />.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// <see langword="true" /> if the enumerator was successfully advanced to the next element;
|
|
/// <see langword="false" /> if the enumerator has passed the end of the collection.
|
|
/// </returns>
|
|
public bool MoveNext()
|
|
{
|
|
var localQueue = _queue;
|
|
|
|
if (_version == localQueue._version && (uint)_index < (uint)localQueue._size)
|
|
{
|
|
Current = localQueue._nodes[_index];
|
|
_index++;
|
|
return true;
|
|
}
|
|
|
|
return MoveNextRare();
|
|
}
|
|
|
|
private bool MoveNextRare()
|
|
{
|
|
if (_version != _queue._version)
|
|
throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
|
|
|
|
_index = _queue._size + 1;
|
|
Current = default;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the element at the current position of the enumerator.
|
|
/// </summary>
|
|
public (TElement Element, TPriority Priority) Current { get; private set; }
|
|
|
|
object IEnumerator.Current => Current;
|
|
|
|
void IEnumerator.Reset()
|
|
{
|
|
if (_version != _queue._version)
|
|
throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
|
|
|
|
_index = 0;
|
|
Current = default;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |