* Add PriorityQueue to System.Collections.Generic (#43957)
This commit adds a new data structure, priority queue.
Fixes #43957
* (draft step, to squash) Modify API reference
In this commit, I modified the API reference using [these guidelines](https://github.com/dotnet/runtime/blob/
4d784693ebc5f91c7eede32170046355ef3969b2/docs/coding-guidelines/updating-ref-source.md), following the advice from @danmosemsft.
The automatically generated code (with `dotnet msbuild /t:GenerateReferenceAssemblySource`) didn't build out of the box and I tweaked it manually.
* (draft step, to squash) Add tests for PriorityQueue
Added generic tests for <int, int> and <string, string>. Removed non-generic tests.
* (draft step, to squash) Add initial implementation
This commit adds the core of the heap implementation for the priority queue.
It doesn't implement the method `EnqueueDequeue`, as I'm not convinced by it. It also doesn't implement the method `CopyTo(Array array, int index)`, leaving this one for later.
* (draft step, to squash) Rename parameters
* (draft step, to squash) Replace `this.nodes` with `_nodes`
* (draft step, to squash) Use an array and handle sizing ourselves
* (draft step, to squash) Create UnorderedItemsCollection lazily
* (draft step, to squash) Deduplicate constructors
* (draft step, to squash) Replace excessive `var` with explicit types
* (draft step, to squash) Remove `this.` in front of methods
* (draft step, to squash) Improve out-of-range-argument exceptions
* (draft step, to squash) Use error messages from .resx
* (draft step, to squash) Use positive case first in try methods
* (draft step, to squash) Implement UnorderedItemsCollection.CopyTo
* (draft step, to squash) Optimize expressions involving Arity
* (draft step, to squash) Adjust implementation to be consistent with reference
* (draft step, to squash) Implement method `EnqueueDequeue`
* (draft step, to squash) Make EnsureCapacity return int
* (draft step, to squash) Simplify lazy initialization of _unorderedItems
* (draft step, to squash) Use `out _` discard for unused properties
* (draft step, to squash) Relax null checks on elements and priorities
* (draft step, to squash) Simplify method SetCapacity
* (draft step, to squash) Remove MethodImplOptions.AggressiveInlining attributed
* (draft step, to squash) Use Array.Empty if the initial capacity is zero
* (draft step, to squash) Simplify UnorderedItemsCollection.Enumerator declaration
* (draft step, to squash) Simplify GetEnumerator methods
* (draft step, to squash) Optimize EnqueueRange methods
* (draft step, to squash) Capitalize members of (TElement, TPriority)[]
* (draft step, to squash) Improve resize constants
* (draft step, to squash) Remove redundant `.this`
* (draft step, to squash) Optimize EnqueueDequeue
* (draft step, to squash) Reduce indentation
* (draft step, to squash) Simplify math expressions
* (draft step, to squash) Remove the PutAt helper method
* (draft step, to squash) Make the UnorderedItemsCollection constructor internal
* (draft step, to squash) Clear last node slot on removal
* (draft step, to squash) Improve next growth capacity computation
* (draft step, to squash) Optimize Enqueue method
* (draft step, to squash) Make UnorderedItemsCollection.CopyTo implemented explicitly on ICollection
* (draft step, to squash) Improve priority queue tests
* (draft step, to squash) Drop redundant casting
* (draft step, to squash) Cosmetic improvements
* (draft step, to squash) Change signature of UnorderedItemsCollection
* (draft step, to squash) Add test PriorityQueue_Generic_EnqueueDequeue_EqualToMin
* (draft step, to squash) Add tests of enqueue null functionality
* (draft step, to squash) Add test PriorityQueue_Generic_EnsureCapacity_Negative
* (draft step, to squash) Check underlying buffer length in tests with reflection
* (draft step, to squash) Check enumeration invalidation
* (draft step, to squash) Simplify code and improve documentation
void System.Collections.IEnumerator.Reset() { }
}
}
+
+ public partial class PriorityQueue<TElement, TPriority>
+ {
+ public PriorityQueue() { }
+ public PriorityQueue(System.Collections.Generic.IComparer<TPriority>? comparer) { }
+ public PriorityQueue(System.Collections.Generic.IEnumerable<(TElement element, TPriority priority)> items) { }
+ public PriorityQueue(System.Collections.Generic.IEnumerable<(TElement element, TPriority priority)> items, System.Collections.Generic.IComparer<TPriority>? comparer) { }
+ public PriorityQueue(int initialCapacity) { }
+ public PriorityQueue(int initialCapacity, System.Collections.Generic.IComparer<TPriority>? comparer) { }
+ public System.Collections.Generic.IComparer<TPriority> Comparer { get { throw null; } }
+ public int Count { get { throw null; } }
+ public System.Collections.Generic.PriorityQueue<TElement, TPriority>.UnorderedItemsCollection UnorderedItems { get { throw null; } }
+ public void Clear() { }
+ public TElement Dequeue() { throw null; }
+ public void Enqueue(TElement element, TPriority priority) { }
+ public TElement EnqueueDequeue(TElement element, TPriority priority) { throw null; }
+ public void EnqueueRange(System.Collections.Generic.IEnumerable<(TElement element, TPriority priority)> items) { }
+ public void EnqueueRange(System.Collections.Generic.IEnumerable<TElement> elements, TPriority priority) { }
+ public int EnsureCapacity(int capacity) { throw null; }
+ public TElement Peek() { throw null; }
+ public void TrimExcess() { }
+ public bool TryDequeue([System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TElement element, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TPriority priority) { throw null; }
+ public bool TryPeek([System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TElement element, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TPriority priority) { throw null; }
+ public sealed partial class UnorderedItemsCollection : System.Collections.Generic.IEnumerable<(TElement element, TPriority priority)>, System.Collections.Generic.IReadOnlyCollection<(TElement element, TPriority priority)>, System.Collections.ICollection, System.Collections.IEnumerable
+ {
+ internal UnorderedItemsCollection(PriorityQueue<TElement, TPriority> queue) { }
+ public int Count { get { throw null; } }
+ bool System.Collections.ICollection.IsSynchronized { get { throw null; } }
+ object System.Collections.ICollection.SyncRoot { get { throw null; } }
+ void ICollection.CopyTo(System.Array array, int index) { }
+ public System.Collections.Generic.PriorityQueue<TElement, TPriority>.UnorderedItemsCollection.Enumerator GetEnumerator() { throw null; }
+ System.Collections.Generic.IEnumerator<(TElement element, TPriority priority)> System.Collections.Generic.IEnumerable<(TElement element, TPriority priority)>.GetEnumerator() { throw null; }
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
+ public partial struct Enumerator : System.Collections.Generic.IEnumerator<(TElement element, TPriority priority)>, System.Collections.IEnumerator, System.IDisposable
+ {
+ (TElement element, TPriority priority) IEnumerator<(TElement element, TPriority priority)>.Current { get { throw null; } }
+ public void Dispose() { }
+ public bool MoveNext() { throw null; }
+ public (TElement element, TPriority priority) Current { get { throw null; } }
+ object System.Collections.IEnumerator.Current { get { throw null; } }
+ void System.Collections.IEnumerator.Reset() { }
+ }
+ }
+ }
+
public partial class Queue<T> : System.Collections.Generic.IEnumerable<T>, System.Collections.Generic.IReadOnlyCollection<T>, System.Collections.ICollection, System.Collections.IEnumerable
{
public Queue() { }
<Compile Include="$(CoreLibSharedDir)System\Collections\Generic\IDictionaryDebugView.cs"
Link="Common\System\Collections\Generic\IDictionaryDebugView.cs" />
<Compile Include="System\Collections\Generic\LinkedList.cs" />
+ <Compile Include="System\Collections\Generic\PriorityQueue.cs" />
<Compile Include="System\Collections\Generic\Queue.cs" />
<Compile Include="System\Collections\Generic\QueueDebugView.cs" />
<Compile Include="System\Collections\Generic\SortedDictionary.cs" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Generic
+{
+ /// <summary>
+ /// Represents a data structure in which each element has an associated priority
+ /// that determines the order in which the pair is dequeued.
+ /// </summary>
+ /// <typeparam name="TElement">The type of the element.</typeparam>
+ /// <typeparam name="TPriority">The type of the priority.</typeparam>
+ public class PriorityQueue<TElement, TPriority>
+ {
+ /// <summary>
+ /// Represents an implicit heap-ordered complete d-ary tree, stored as an array.
+ /// </summary>
+ private (TElement Element, TPriority Priority)[] _nodes;
+
+ private UnorderedItemsCollection? _unorderedItems;
+
+ /// <summary>
+ /// The number of nodes in the heap.
+ /// </summary>
+ private int _size;
+
+ /// <summary>
+ /// Version updated on mutation to help validate enumerators operate on a consistent state.
+ /// </summary>
+ private int _version;
+
+ /// <summary>
+ /// When the underlying buffer for the heap nodes grows to accomodate more nodes,
+ /// this is the minimum the capacity will grow by.
+ /// </summary>
+ private const int MinimumElementsToGrowBy = 4;
+
+ /// <summary>
+ /// The index at which the heap root is maintained.
+ /// </summary>
+ private const int RootIndex = 0;
+
+ /// <summary>
+ /// Specifies the arity of the d-ary heap, which here is quaternary.
+ /// </summary>
+ private const int Arity = 4;
+
+ /// <summary>
+ /// The binary logarithm of <see cref="Arity" />.
+ /// </summary>
+ private const int Log2Arity = 2;
+
+ /// <summary>
+ /// Creates an empty priority queue.
+ /// </summary>
+ public PriorityQueue()
+ : this(initialCapacity: 0, comparer: null)
+ {
+ }
+
+ /// <summary>
+ /// Creates an empty priority queue with the specified initial capacity for its underlying array.
+ /// </summary>
+ public PriorityQueue(int initialCapacity)
+ : this(initialCapacity, comparer: null)
+ {
+ }
+
+ /// <summary>
+ /// Creates an empty priority queue with the specified priority comparer.
+ /// </summary>
+ public PriorityQueue(IComparer<TPriority>? comparer)
+ : this(initialCapacity: 0, comparer)
+ {
+ }
+
+ /// <summary>
+ /// Creates an empty priority queue with the specified priority comparer and
+ /// the specified initial capacity for its underlying array.
+ /// </summary>
+ public PriorityQueue(int initialCapacity, IComparer<TPriority>? comparer)
+ {
+ if (initialCapacity < 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(initialCapacity), initialCapacity, SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ _nodes = (initialCapacity == 0)
+ ? Array.Empty<(TElement, TPriority)>()
+ : new (TElement, TPriority)[initialCapacity];
+
+ Comparer = comparer ?? Comparer<TPriority>.Default;
+ }
+
+ /// <summary>
+ /// Creates a priority queue populated with the specified elements and priorities.
+ /// </summary>
+ public PriorityQueue(IEnumerable<(TElement element, TPriority priority)> items)
+ : this(items, comparer: null)
+ {
+ }
+
+ /// <summary>
+ /// Creates a priority queue populated with the specified elements and priorities,
+ /// and with the specified priority comparer.
+ /// </summary>
+ public PriorityQueue(IEnumerable<(TElement element, TPriority priority)> items, IComparer<TPriority>? comparer)
+ {
+ if (items is null)
+ {
+ throw new ArgumentNullException(nameof(items));
+ }
+
+ _nodes = EnumerableHelpers.ToArray(items, out _size);
+ Comparer = comparer ?? Comparer<TPriority>.Default;
+
+ if (_size > 1)
+ {
+ Heapify();
+ }
+ }
+
+ /// <summary>
+ /// Gets the current amount of items in the priority queue.
+ /// </summary>
+ public int Count => _size;
+
+ /// <summary>
+ /// Gets the priority comparer of the priority queue.
+ /// </summary>
+ public IComparer<TPriority> Comparer { get; }
+
+ /// <summary>
+ /// Gets a collection that enumerates the elements of the queue.
+ /// </summary>
+ public UnorderedItemsCollection UnorderedItems => _unorderedItems ??= new UnorderedItemsCollection(this);
+
+ /// <summary>
+ /// Enqueues the specified element and associates it with the specified priority.
+ /// </summary>
+ public void Enqueue(TElement element, TPriority priority)
+ {
+ EnsureEnoughCapacityBeforeAddingNode();
+
+ // 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.
+ _size++;
+ _version++;
+
+ // Restore the heap order
+ int lastNodeIndex = GetLastNodeIndex();
+ MoveUp((element, priority), lastNodeIndex);
+ }
+
+ /// <summary>
+ /// Gets the element associated with the minimal priority.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">The queue is empty.</exception>
+ public TElement Peek()
+ {
+ if (_size == 0)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EmptyQueue);
+ }
+
+ return _nodes[RootIndex].Element;
+ }
+
+ /// <summary>
+ /// Dequeues the element associated with the minimal priority.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">The queue is empty.</exception>
+ public TElement Dequeue()
+ {
+ if (_size == 0)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EmptyQueue);
+ }
+
+ TElement element = _nodes[RootIndex].Element;
+ Remove(RootIndex);
+ return element;
+ }
+
+ /// <summary>
+ /// Dequeues the element associated with the minimal priority
+ /// </summary>
+ /// <returns>
+ /// <see langword="true"/> if the priority queue is non-empty; <see langword="false"/> otherwise.
+ /// </returns>
+ public bool TryDequeue([MaybeNullWhen(false)] out TElement element, [MaybeNullWhen(false)] out TPriority priority)
+ {
+ if (_size != 0)
+ {
+ (element, priority) = _nodes[RootIndex];
+ Remove(RootIndex);
+ return true;
+ }
+
+ element = default;
+ priority = default;
+ return false;
+ }
+
+ /// <summary>
+ /// Gets the element associated with the minimal priority.
+ /// </summary>
+ /// <returns>
+ /// <see langword="true"/> if the priority queue is non-empty; <see langword="false"/> otherwise.
+ /// </returns>
+ public bool TryPeek([MaybeNullWhen(false)] out TElement element, [MaybeNullWhen(false)] out TPriority priority)
+ {
+ if (_size != 0)
+ {
+ (element, priority) = _nodes[RootIndex];
+ return true;
+ }
+
+ element = default;
+ priority = default;
+ return false;
+ }
+
+ /// <summary>
+ /// Combined enqueue/dequeue operation, generally more efficient than sequential Enqueue/Dequeue calls.
+ /// </summary>
+ public TElement EnqueueDequeue(TElement element, TPriority priority)
+ {
+ (TElement Element, TPriority Priority) root = _nodes[RootIndex];
+
+ if (Comparer.Compare(priority, root.Priority) <= 0)
+ {
+ return element;
+ }
+ else
+ {
+ (TElement Element, TPriority Priority) newRoot = (element, priority);
+ _nodes[RootIndex] = newRoot;
+
+ MoveDown(newRoot, RootIndex);
+ _version++;
+
+ return root.Element;
+ }
+ }
+
+ /// <summary>
+ /// Enqueues a collection of element/priority pairs.
+ /// </summary>
+ public void EnqueueRange(IEnumerable<(TElement Element, TPriority Priority)> items)
+ {
+ if (items is null)
+ {
+ throw new ArgumentNullException(nameof(items));
+ }
+
+ if (_size == 0)
+ {
+ _nodes = EnumerableHelpers.ToArray(items, out _size);
+
+ if (_size > 1)
+ {
+ Heapify();
+ }
+ }
+ else
+ {
+ foreach ((TElement element, TPriority priority) in items)
+ {
+ Enqueue(element, priority);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Enqueues a collection of elements, each associated with the specified priority.
+ /// </summary>
+ public void EnqueueRange(IEnumerable<TElement> elements, TPriority priority)
+ {
+ if (elements is null)
+ {
+ throw new ArgumentNullException(nameof(elements));
+ }
+
+ if (_size == 0)
+ {
+ using (IEnumerator<TElement> enumerator = elements.GetEnumerator())
+ {
+ if (enumerator.MoveNext())
+ {
+ _nodes = new (TElement, TPriority)[MinimumElementsToGrowBy];
+ _nodes[0] = (enumerator.Current, priority);
+ _size = 1;
+
+ while (enumerator.MoveNext())
+ {
+ EnsureEnoughCapacityBeforeAddingNode();
+ _nodes[_size++] = (enumerator.Current, priority);
+ }
+
+ if (_size > 1)
+ {
+ Heapify();
+ }
+ }
+ }
+ }
+ else
+ {
+ foreach (TElement element in elements)
+ {
+ Enqueue(element, priority);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Removes all items from the priority queue.
+ /// </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 priority queue has the specified capacity
+ /// and resizes its underlying array if necessary.
+ /// </summary>
+ public int EnsureCapacity(int capacity)
+ {
+ if (capacity < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity), capacity, SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (_nodes.Length < capacity)
+ {
+ SetCapacity(Math.Max(capacity, ComputeCapacityForNextGrowth()));
+ }
+
+ return _nodes.Length;
+ }
+
+ /// <summary>
+ /// Sets the capacity to the actual number of items in the priority queue,
+ /// if that is less than 90 percent of current capacity.
+ /// </summary>
+ public void TrimExcess()
+ {
+ int threshold = (int)(_nodes.Length * 0.9);
+ if (_size < threshold)
+ {
+ SetCapacity(_size);
+ }
+ }
+
+ private void EnsureEnoughCapacityBeforeAddingNode()
+ {
+ Debug.Assert(_size <= _nodes.Length);
+ if (_size == _nodes.Length)
+ {
+ SetCapacity(ComputeCapacityForNextGrowth());
+ }
+ }
+
+ private int ComputeCapacityForNextGrowth()
+ {
+ const int GrowthFactor = 2;
+ const int MaxArrayLength = 0X7FEFFFFF;
+
+ int newCapacity = Math.Max(_nodes.Length * GrowthFactor, _nodes.Length + MinimumElementsToGrowBy);
+
+ // Allow the structure 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 > MaxArrayLength)
+ {
+ newCapacity = MaxArrayLength;
+ }
+
+ return newCapacity;
+ }
+
+ /// <summary>
+ /// Grows or shrinks the array holding nodes. Capacity must be >= _size.
+ /// </summary>
+ private void SetCapacity(int capacity)
+ {
+ Array.Resize(ref _nodes, capacity);
+ _version++;
+ }
+
+ /// <summary>
+ /// Removes the node at the specified index.
+ /// </summary>
+ private void Remove(int indexOfNodeToRemove)
+ {
+ // The idea is to replace the specified node by the very last
+ // node and shorten the array by one.
+
+ int lastNodeIndex = GetLastNodeIndex();
+ (TElement Element, TPriority Priority) lastNode = _nodes[lastNodeIndex];
+ _nodes[lastNodeIndex] = default;
+ _size--;
+ _version++;
+
+ // In case we wanted to remove the node that was the last one,
+ // we are done.
+
+ if (indexOfNodeToRemove == lastNodeIndex)
+ {
+ return;
+ }
+
+ // Our last node was erased from the array and needs to be
+ // inserted again. Of course, we will overwrite the node we
+ // wanted to remove. After that operation, we will need
+ // to restore the heap property (in general).
+
+ (TElement Element, TPriority Priority) nodeToRemove = _nodes[indexOfNodeToRemove];
+
+ int relation = Comparer.Compare(lastNode.Priority, nodeToRemove.Priority);
+ _nodes[indexOfNodeToRemove] = lastNode;
+
+ if (relation < 0)
+ {
+ MoveUp(lastNode, indexOfNodeToRemove);
+ }
+ else
+ {
+ MoveDown(lastNode, indexOfNodeToRemove);
+ }
+ }
+
+ /// <summary>
+ /// Gets the index of the last node in the heap.
+ /// </summary>
+ private int GetLastNodeIndex() => _size - 1;
+
+ /// <summary>
+ /// Gets the index of an element's parent.
+ /// </summary>
+ private int GetParentIndex(int index) => (index - 1) >> Log2Arity;
+
+ /// <summary>
+ /// Gets the index of the first child of an element.
+ /// </summary>
+ private int GetFirstChildIndex(int index) => Arity * index + 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.
+
+ int lastNodeIndex = GetLastNodeIndex();
+ int lastParentWithChildren = GetParentIndex(lastNodeIndex);
+
+ for (int index = lastParentWithChildren; index >= 0; --index)
+ {
+ MoveDown(_nodes[index], index);
+ }
+ }
+
+ /// <summary>
+ /// Moves a node up in the tree to restore heap order.
+ /// </summary>
+ private void MoveUp((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.
+
+ while (nodeIndex > 0)
+ {
+ int parentIndex = GetParentIndex(nodeIndex);
+ (TElement Element, TPriority Priority) parent = _nodes[parentIndex];
+
+ if (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 MoveDown((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.
+
+ int i;
+ while ((i = GetFirstChildIndex(nodeIndex)) < _size)
+ {
+ // Check if the current node (pointed by 'nodeIndex') should really be extracted
+ // first, or maybe one of its children should be extracted earlier.
+ (TElement Element, TPriority Priority) topChild = _nodes[i];
+ int childrenIndexesLimit = Math.Min(i + Arity, _size);
+ int topChildIndex = i;
+
+ while (++i < childrenIndexesLimit)
+ {
+ (TElement Element, TPriority Priority) child = _nodes[i];
+ if (Comparer.Compare(child.Priority, topChild.Priority) < 0)
+ {
+ topChild = child;
+ topChildIndex = i;
+ }
+ }
+
+ // In case no child needs to be extracted earlier than the current node,
+ // there is nothing more to do - the right spot was found.
+ if (Comparer.Compare(node.priority, topChild.Priority) <= 0)
+ {
+ break;
+ }
+
+ // Move the top child up by one node and now investigate the
+ // node that was considered to be the top child (recursive).
+ _nodes[nodeIndex] = topChild;
+ nodeIndex = topChildIndex;
+ }
+
+ _nodes[nodeIndex] = node;
+ }
+
+ public sealed class UnorderedItemsCollection : IReadOnlyCollection<(TElement element, TPriority priority)>, ICollection
+ {
+ private readonly PriorityQueue<TElement, TPriority> _queue;
+
+ internal UnorderedItemsCollection(PriorityQueue<TElement, TPriority> queue)
+ {
+ _queue = queue;
+ }
+
+ public int Count => _queue._size;
+ object ICollection.SyncRoot => this;
+ bool ICollection.IsSynchronized => false;
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ if (array is null)
+ {
+ throw new ArgumentNullException(nameof(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_Index);
+ }
+
+ 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 struct Enumerator : IEnumerator<(TElement element, TPriority priority)>
+ {
+ private readonly PriorityQueue<TElement, TPriority> _queue;
+ private readonly int _version;
+
+ private int _index;
+ private (TElement element, TPriority priority)? _currentElement;
+
+ private const int FirstCallToEnumerator = -2;
+ private const int EndOfEnumeration = -1;
+
+ internal Enumerator(PriorityQueue<TElement, TPriority> queue)
+ {
+ _queue = queue;
+ _version = queue._version;
+ _index = FirstCallToEnumerator;
+ _currentElement = default;
+ }
+
+ public void Dispose()
+ {
+ _index = EndOfEnumeration;
+ }
+
+ public bool MoveNext()
+ {
+ if (_version != _queue._version)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+ }
+
+ if (_index == FirstCallToEnumerator)
+ {
+ if (_queue._size > 0)
+ {
+ _index = 0;
+ _currentElement = _queue._nodes[_index];
+ return true;
+ }
+ else
+ {
+ _index = EndOfEnumeration;
+ return false;
+ }
+ }
+
+ if (_index == EndOfEnumeration)
+ {
+ return false;
+ }
+
+ // advance enumerator
+ _index++;
+
+ if (_index < _queue._size)
+ {
+ _currentElement = _queue._nodes[_index];
+ return true;
+ }
+ else
+ {
+ _index = EndOfEnumeration;
+ _currentElement = default;
+ return false;
+ }
+ }
+
+ public (TElement element, TPriority priority) Current
+ {
+ get
+ {
+ if (_index < 0)
+ {
+ ThrowEnumerationNotStartedOrEnded();
+ }
+ return _currentElement!.Value;
+ }
+ }
+
+ private void ThrowEnumerationNotStartedOrEnded()
+ {
+ Debug.Assert(_index == FirstCallToEnumerator || _index == EndOfEnumeration);
+
+ string message = _index == FirstCallToEnumerator
+ ? SR.InvalidOperation_EnumNotStarted
+ : SR.InvalidOperation_EnumEnded;
+
+ throw new InvalidOperationException(message);
+ }
+
+ object IEnumerator.Current => Current;
+
+ void IEnumerator.Reset()
+ {
+ if (_version != _queue._version)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+ }
+
+ _index = FirstCallToEnumerator;
+ _currentElement = default;
+ }
+ }
+
+ public Enumerator GetEnumerator()
+ => new Enumerator(_queue);
+
+ IEnumerator<(TElement element, TPriority priority)> IEnumerable<(TElement element, TPriority priority)>.GetEnumerator()
+ => GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => GetEnumerator();
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+
+namespace System.Collections.Tests
+{
+ public abstract class PriorityQueue_Generic_Tests<TElement, TPriority> : TestBase<(TElement, TPriority)>
+ {
+ #region Helper methods
+
+ protected IEnumerable<(TElement, TPriority)> GenericIEnumerableFactory(int count)
+ {
+ const int MagicValue = 34;
+ int seed = count * MagicValue;
+ for (int i = 0; i < count; i++)
+ {
+ yield return CreateT(seed++);
+ }
+ }
+
+ protected PriorityQueue<TElement, TPriority> GenericPriorityQueueFactory(
+ int initialCapacity, int countOfItemsToGenerate, out List<(TElement element, TPriority priority)> generatedItems)
+ {
+ generatedItems = this.GenericIEnumerableFactory(countOfItemsToGenerate).ToList();
+
+ var queue = new PriorityQueue<TElement, TPriority>(initialCapacity);
+ foreach (var (element, priority) in generatedItems)
+ {
+ queue.Enqueue(element, priority);
+ }
+
+ return queue;
+ }
+
+ #endregion
+
+ #region Constructors
+
+ [Fact]
+ public void PriorityQueue_Generic_Constructor()
+ {
+ var queue = new PriorityQueue<TElement, TPriority>();
+
+ Assert.Equal(expected: 0, queue.Count);
+ Assert.Empty(queue.UnorderedItems);
+ Assert.Equal(queue.Comparer, Comparer<TPriority>.Default);
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidCollectionSizes))]
+ public void PriorityQueue_Generic_Constructor_int(int initialCapacity)
+ {
+ var queue = new PriorityQueue<TElement, TPriority>(initialCapacity);
+
+ Assert.Empty(queue.UnorderedItems);
+ }
+
+ [Fact]
+ public void PriorityQueue_Generic_Constructor_int_Negative_ThrowsArgumentOutOfRangeException()
+ {
+ AssertExtensions.Throws<ArgumentOutOfRangeException>("initialCapacity", () => new PriorityQueue<TElement, TPriority>(-1));
+ AssertExtensions.Throws<ArgumentOutOfRangeException>("initialCapacity", () => new PriorityQueue<TElement, TPriority>(int.MinValue));
+ }
+
+ [Fact]
+ public void PriorityQueue_Generic_Constructor_IComparer()
+ {
+ IComparer<TPriority> comparer = Comparer<TPriority>.Default;
+ var queue = new PriorityQueue<TElement, TPriority>(comparer);
+
+ Assert.Equal(comparer, queue.Comparer);
+ }
+
+ [Fact]
+ public void PriorityQueue_Generic_Constructor_IComparer_Null()
+ {
+ var queue = new PriorityQueue<TElement, TPriority>((IComparer<TPriority>)null);
+ Assert.Equal(Comparer<TPriority>.Default, queue.Comparer);
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidCollectionSizes))]
+ public void PriorityQueue_Generic_Constructor_int_IComparer(int initialCapacity)
+ {
+ IComparer<TPriority> comparer = Comparer<TPriority>.Default;
+ var queue = new PriorityQueue<TElement, TPriority>(initialCapacity);
+
+ Assert.Empty(queue.UnorderedItems);
+ Assert.Equal(comparer, queue.Comparer);
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidCollectionSizes))]
+ public void PriorityQueue_Generic_Constructor_IEnumerable(int count)
+ {
+ HashSet<(TElement, TPriority)> itemsToEnqueue = this.GenericIEnumerableFactory(count).ToHashSet();
+ PriorityQueue<TElement, TPriority> queue = new PriorityQueue<TElement, TPriority>(itemsToEnqueue);
+ Assert.True(itemsToEnqueue.SetEquals(queue.UnorderedItems));
+ }
+
+ #endregion
+
+ #region Enqueue, Dequeue, Peek
+
+ [Theory]
+ [MemberData(nameof(ValidCollectionSizes))]
+ public void PriorityQueue_Generic_Enqueue(int count)
+ {
+ PriorityQueue<TElement, TPriority> queue = GenericPriorityQueueFactory(count, count, out var generatedItems);
+ HashSet<(TElement, TPriority)> expectedItems = generatedItems.ToHashSet();
+
+ Assert.Equal(count, queue.Count);
+ var actualItems = queue.UnorderedItems.ToArray();
+ Assert.True(expectedItems.SetEquals(actualItems));
+ }
+
+ [Fact]
+ public void PriorityQueue_Generic_Dequeue_EmptyCollection()
+ {
+ var queue = new PriorityQueue<TElement, TPriority>();
+
+ Assert.False(queue.TryDequeue(out _, out _));
+ Assert.Throws<InvalidOperationException>(() => queue.Dequeue());
+ }
+
+ [Fact]
+ public void PriorityQueue_Generic_Peek_EmptyCollection()
+ {
+ var queue = new PriorityQueue<TElement, TPriority>();
+
+ Assert.False(queue.TryPeek(out _, out _));
+ Assert.Throws<InvalidOperationException>(() => queue.Peek());
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidPositiveCollectionSizes))]
+ public void PriorityQueue_Generic_Peek_PositiveCount(int count)
+ {
+ IReadOnlyCollection<(TElement, TPriority)> itemsToEnqueue = this.GenericIEnumerableFactory(count).ToArray();
+ (TElement element, TPriority priority) expectedPeek = itemsToEnqueue.First();
+ PriorityQueue<TElement, TPriority> queue = new PriorityQueue<TElement, TPriority>();
+
+ foreach (var (element, priority) in itemsToEnqueue)
+ {
+ if (queue.Comparer.Compare(priority, expectedPeek.priority) < 0)
+ {
+ expectedPeek = (element, priority);
+ }
+
+ queue.Enqueue(element, priority);
+
+ var actualPeekElement = queue.Peek();
+ var actualTryPeekSuccess = queue.TryPeek(out TElement actualTryPeekElement, out TPriority actualTryPeekPriority);
+
+ Assert.Equal(expectedPeek.element, actualPeekElement);
+ Assert.True(actualTryPeekSuccess);
+ Assert.Equal(expectedPeek.element, actualTryPeekElement);
+ Assert.Equal(expectedPeek.priority, actualTryPeekPriority);
+ }
+ }
+
+ [Theory]
+ [InlineData(0, 5)]
+ [InlineData(1, 1)]
+ [InlineData(3, 100)]
+ public void PriorityQueue_Generic_PeekAndDequeue(int initialCapacity, int count)
+ {
+ PriorityQueue<TElement, TPriority> queue = this.GenericPriorityQueueFactory(initialCapacity, count, out var generatedItems);
+
+ var expectedPeekPriorities = generatedItems
+ .Select(x => x.priority)
+ .OrderBy(x => x, queue.Comparer)
+ .ToArray();
+
+ for (var i = 0; i < count; ++i)
+ {
+ var expectedPeekPriority = expectedPeekPriorities[i];
+
+ var actualTryPeekSuccess = queue.TryPeek(out TElement actualTryPeekElement, out TPriority actualTryPeekPriority);
+ var actualTryDequeueSuccess = queue.TryDequeue(out TElement actualTryDequeueElement, out TPriority actualTryDequeuePriority);
+
+ Assert.True(actualTryPeekSuccess);
+ Assert.True(actualTryDequeueSuccess);
+ Assert.Equal(expectedPeekPriority, actualTryPeekPriority);
+ Assert.Equal(expectedPeekPriority, actualTryDequeuePriority);
+ }
+
+ Assert.Equal(expected: 0, queue.Count);
+ Assert.False(queue.TryPeek(out _, out _));
+ Assert.False(queue.TryDequeue(out _, out _));
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidCollectionSizes))]
+ public void PriorityQueue_Generic_EnqueueRange_IEnumerable(int count)
+ {
+ HashSet<(TElement, TPriority)> itemsToEnqueue = this.GenericIEnumerableFactory(count).ToHashSet();
+ PriorityQueue<TElement, TPriority> queue = new PriorityQueue<TElement, TPriority>();
+
+ queue.EnqueueRange(itemsToEnqueue);
+
+ Assert.True(itemsToEnqueue.SetEquals(queue.UnorderedItems));
+ }
+
+ #endregion
+
+ #region Clear
+
+ [Theory]
+ [MemberData(nameof(ValidCollectionSizes))]
+ public void PriorityQueue_Generic_Clear(int count)
+ {
+ PriorityQueue<TElement, TPriority> queue = this.GenericPriorityQueueFactory(initialCapacity: 0, count, out _);
+
+ Assert.Equal(count, queue.Count);
+ queue.Clear();
+ Assert.Equal(expected: 0, queue.Count);
+ }
+
+ #endregion
+
+ #region EnsureCapacity, TrimExcess
+
+ [Fact]
+ public void PriorityQueue_Generic_EnsureCapacity_Negative()
+ {
+ PriorityQueue<TElement, TPriority> queue = new PriorityQueue<TElement, TPriority>();
+ AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => queue.EnsureCapacity(-1));
+ AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => queue.EnsureCapacity(int.MinValue));
+ }
+
+ [Theory]
+ [InlineData(0, 0)]
+ [InlineData(0, 5)]
+ [InlineData(1, 1)]
+ [InlineData(3, 100)]
+ public void PriorityQueue_Generic_TrimExcess_ValidQueueThatHasntBeenRemovedFrom(int initialCapacity, int count)
+ {
+ PriorityQueue<TElement, TPriority> queue = this.GenericPriorityQueueFactory(initialCapacity, count, out _);
+ queue.TrimExcess();
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidCollectionSizes))]
+ public void PriorityQueue_Generic_TrimExcess_Repeatedly(int count)
+ {
+ PriorityQueue<TElement, TPriority> queue = this.GenericPriorityQueueFactory(initialCapacity: 0, count, out _);
+
+ Assert.Equal(count, queue.Count);
+ queue.TrimExcess();
+ queue.TrimExcess();
+ queue.TrimExcess();
+ Assert.Equal(count, queue.Count);
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidPositiveCollectionSizes))]
+ public void PriorityQueue_Generic_EnsureCapacityAndTrimExcess(int count)
+ {
+ IReadOnlyCollection<(TElement, TPriority)> itemsToEnqueue = this.GenericIEnumerableFactory(count).ToArray();
+ PriorityQueue<TElement, TPriority> queue = new PriorityQueue<TElement, TPriority>();
+ int expectedCount = 0;
+ Random random = new Random(Seed: 34);
+ int getNextEnsureCapacity() => random.Next(0, count * 2);
+ void trimAndEnsureCapacity()
+ {
+ queue.TrimExcess();
+
+ int capacityAfterEnsureCapacity = queue.EnsureCapacity(getNextEnsureCapacity());
+ Assert.Equal(capacityAfterEnsureCapacity, GetUnderlyingBufferCapacity(queue));
+
+ int capacityAfterTrimExcess = (queue.Count < (int)(capacityAfterEnsureCapacity * 0.9)) ? queue.Count : capacityAfterEnsureCapacity;
+ queue.TrimExcess();
+ Assert.Equal(capacityAfterTrimExcess, GetUnderlyingBufferCapacity(queue));
+ };
+
+ foreach (var (element, priority) in itemsToEnqueue)
+ {
+ trimAndEnsureCapacity();
+ queue.Enqueue(element, priority);
+ expectedCount++;
+ Assert.Equal(expectedCount, queue.Count);
+ }
+
+ while (expectedCount > 0)
+ {
+ queue.Dequeue();
+ trimAndEnsureCapacity();
+ expectedCount--;
+ Assert.Equal(expectedCount, queue.Count);
+ }
+
+ trimAndEnsureCapacity();
+ Assert.Equal(0, queue.Count);
+ }
+
+ private static int GetUnderlyingBufferCapacity(PriorityQueue<TElement, TPriority> queue)
+ {
+ FieldInfo nodesType = queue.GetType().GetField("_nodes", BindingFlags.NonPublic | BindingFlags.Instance);
+ var nodes = ((TElement Element, TPriority Priority)[])nodesType.GetValue(queue);
+ return nodes.Length;
+ }
+
+ #endregion
+
+ #region Enumeration
+
+ [Theory]
+ [MemberData(nameof(ValidPositiveCollectionSizes))]
+ public void PriorityQueue_Enumeration_OrderingIsConsistent(int count)
+ {
+ PriorityQueue<TElement, TPriority> queue = this.GenericPriorityQueueFactory(initialCapacity: 0, count, out _);
+
+ (TElement, TPriority)[] firstEnumeration = queue.UnorderedItems.ToArray();
+ (TElement, TPriority)[] secondEnumeration = queue.UnorderedItems.ToArray();
+
+ Assert.Equal(firstEnumeration.Length, count);
+ Assert.True(firstEnumeration.SequenceEqual(secondEnumeration));
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidPositiveCollectionSizes))]
+ public void PriorityQueue_Enumeration_InvalidationOnModifiedCollection(int count)
+ {
+ IReadOnlyCollection<(TElement, TPriority)> itemsToEnqueue = this.GenericIEnumerableFactory(count).ToArray();
+ PriorityQueue<TElement, TPriority> queue = new PriorityQueue<TElement, TPriority>();
+ queue.EnqueueRange(itemsToEnqueue.Take(count - 1));
+ var enumerator = queue.UnorderedItems.GetEnumerator();
+
+ (TElement element, TPriority priority) = itemsToEnqueue.Last();
+ queue.Enqueue(element, priority);
+ Assert.Throws<InvalidOperationException>(() => enumerator.MoveNext());
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidPositiveCollectionSizes))]
+ public void PriorityQueue_Enumeration_InvalidationOnModifiedCapacity(int count)
+ {
+ PriorityQueue<TElement, TPriority> queue = this.GenericPriorityQueueFactory(initialCapacity: 0, count, out _);
+ var enumerator = queue.UnorderedItems.GetEnumerator();
+
+ int capacityBefore = GetUnderlyingBufferCapacity(queue);
+ queue.EnsureCapacity(count * 2 + 4);
+ int capacityAfter = GetUnderlyingBufferCapacity(queue);
+
+ Assert.NotEqual(capacityBefore, capacityAfter);
+ Assert.Throws<InvalidOperationException>(() => enumerator.MoveNext());
+ }
+
+ #endregion
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Collections.Tests
+{
+ public class PriorityQueue_Generic_Tests_string_string : PriorityQueue_Generic_Tests<string, string>
+ {
+ protected override (string, string) CreateT(int seed)
+ {
+ var element = this.CreateString(seed);
+ var priority = this.CreateString(seed);
+ return (element, priority);
+ }
+
+ protected string CreateString(int seed)
+ {
+ int stringLength = seed % 10 + 5;
+ Random rand = new Random(seed);
+ byte[] bytes = new byte[stringLength];
+ rand.NextBytes(bytes);
+ return Convert.ToBase64String(bytes);
+ }
+ }
+
+ public class PriorityQueue_Generic_Tests_int_int : PriorityQueue_Generic_Tests<int, int>
+ {
+ protected override (int, int) CreateT(int seed)
+ {
+ var element = this.CreateInt(seed);
+ var priority = this.CreateInt(seed);
+ return (element, priority);
+ }
+
+ protected int CreateInt(int seed) => new Random(seed).Next();
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Collections.Tests
+{
+ public class PriorityQueue_NonGeneric_Tests : TestBase
+ {
+ protected PriorityQueue<string, int> SmallPriorityQueueFactory(out HashSet<(string, int)> items)
+ {
+ items = new HashSet<(string, int)>
+ {
+ ("one", 1),
+ ("two", 2),
+ ("three", 3)
+ };
+ var queue = new PriorityQueue<string, int>(items);
+
+ return queue;
+ }
+
+ [Fact]
+ public void PriorityQueue_Generic_EnqueueDequeue_SmallerThanMin()
+ {
+ PriorityQueue<string, int> queue = SmallPriorityQueueFactory(out HashSet<(string, int)> enqueuedItems);
+
+ string actualElement = queue.EnqueueDequeue("zero", 0);
+
+ Assert.Equal("zero", actualElement);
+ Assert.True(enqueuedItems.SetEquals(queue.UnorderedItems));
+ }
+
+ [Fact]
+ public void PriorityQueue_Generic_EnqueueDequeue_LargerThanMin()
+ {
+ PriorityQueue<string, int> queue = SmallPriorityQueueFactory(out HashSet<(string, int)> enqueuedItems);
+
+ string actualElement = queue.EnqueueDequeue("four", 4);
+
+ Assert.Equal("one", actualElement);
+ Assert.Equal("two", queue.Dequeue());
+ Assert.Equal("three", queue.Dequeue());
+ Assert.Equal("four", queue.Dequeue());
+ }
+
+ [Fact]
+ public void PriorityQueue_Generic_EnqueueDequeue_EqualToMin()
+ {
+ PriorityQueue<string, int> queue = SmallPriorityQueueFactory(out HashSet<(string, int)> enqueuedItems);
+
+ string actualElement = queue.EnqueueDequeue("one-not-to-enqueue", 1);
+
+ Assert.Equal("one-not-to-enqueue", actualElement);
+ Assert.True(enqueuedItems.SetEquals(queue.UnorderedItems));
+ }
+
+ [Fact]
+ public void PriorityQueue_Generic_Constructor_IEnumerable_Null()
+ {
+ (string, int)[] itemsToEnqueue = new(string, int)[] { (null, 0), ("one", 1) } ;
+ PriorityQueue<string, int> queue = new PriorityQueue<string, int>(itemsToEnqueue);
+ Assert.Null(queue.Dequeue());
+ Assert.Equal("one", queue.Dequeue());
+ }
+
+ [Fact]
+ public void PriorityQueue_Generic_Enqueue_Null()
+ {
+ PriorityQueue<string, int> queue = new PriorityQueue<string, int>();
+
+ queue.Enqueue(element: null, 1);
+ queue.Enqueue(element: "zero", 0);
+ queue.Enqueue(element: "two", 2);
+
+ Assert.Equal("zero", queue.Dequeue());
+ Assert.Null(queue.Dequeue());
+ Assert.Equal("two", queue.Dequeue());
+ }
+
+ [Fact]
+ public void PriorityQueue_Generic_EnqueueRange_Null()
+ {
+ PriorityQueue<string, int> queue = new PriorityQueue<string, int>();
+
+ queue.EnqueueRange(new string[] { null, null, null }, 0);
+ queue.EnqueueRange(new string[] { "not null" }, 1);
+ queue.EnqueueRange(new string[] { null, null, null }, 0);
+
+ for (int i = 0; i < 6; ++i)
+ {
+ Assert.Null(queue.Dequeue());
+ }
+
+ Assert.Equal("not null", queue.Dequeue());
+ }
+ }
+}
<Compile Include="Generic\List\List.Generic.Tests.Find.cs" />
<Compile Include="Generic\List\List.Generic.Tests.Remove.cs" />
<Compile Include="Generic\List\List.Generic.Tests.Sort.cs" />
+ <Compile Include="Generic\PriorityQueue\PriorityQueue.Generic.cs" />
+ <Compile Include="Generic\PriorityQueue\PriorityQueue.Generic.Tests.cs" />
+ <Compile Include="Generic\PriorityQueue\PriorityQueue.Tests.cs" />
<Compile Include="Generic\Queue\Queue.Generic.cs" />
<Compile Include="Generic\Queue\Queue.Generic.Tests.cs" />
<Compile Include="Generic\Queue\Queue.Tests.cs" />