-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: Add a DequeueEnqueue method to the PriorityQueue Type #75070
Comments
Tagging subscribers to this area: @dotnet/area-system-collections Issue DetailsBackground and motivationWhen working with d-ary heap structures, insert-then-extract and extract-then-insert operations can be done which are generally more efficient than sequentially executing the separate insert/extract operations. The existing The term pop-push is sometimes used for the extract-then-insert implementation. The python implementation calls this One example usage of the new method would be a scenario where the elements of the priority queue represent nodes from different linked lists. If each operation to dequeue the next element is always followed by inserting the next node that is pointed at, the optimized extract-then-insert would be the preferable choice. API Proposalnamespace System.Collections.Generic;
public class PriorityQueue<TElement, TPriority> : IEnumerable<T>
{
/// <summary>
/// Removes the minimal element and then immediately 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>
/// <returns>The minimal element removed before performing the enqueue operation.</returns>
/// <remarks>
/// Implements an extract-then-insert heap operation that is generally more efficient
/// than sequencing Dequeue and Enqueue operations
/// </remarks>
public TElement DequeueEnqueue(TElement element, TPriority priority)
{
if (_size == 0)
throw new InvalidOperationException(SR.InvalidOperation_EmptyQueue);
(TElement Element, TPriority Priority) root = _nodes[0];
if (_comparer == null)
{
if (Comparer<TPriority>.Default.Compare(priority, root.Priority) > 0)
{
MoveDownDefaultComparer((element, priority), 0);
}
else
{
_nodes[0] = (element, priority);
}
}
else
{
if (_comparer.Compare(priority, root.Priority) > 0)
{
MoveDownCustomComparer((element, priority), 0);
}
else
{
_nodes[0] = (element, priority);
}
}
_version++;
return root.Element;
}
} API Usagepublic record ListNode(ListNode? Next, int Value);
var priorityQueue = new PriorityQueue<ListNode, int>(initialElements);
while (priorityQueue.TryPeek(out var peekedElement, out _))
{
var minElement = peekedElement.Next != null
? priorityQueue.DequeueEnqueue(peekedElement.Next, peekedElement.Next.Value)
: priorityQueue.Dequeue();
// ... logic that uses minElement ....
} Alternative DesignsN/A RisksThe new method implementation must be correctly optimized in order to achieve better performance than executing the Dequeue() and Enqueue(..) methods sequentially.
|
Proposal looks good to me. Note we're not sure when this will get picked up by API review. Assuming it gets approved are you interested in creating PR with implementation and tests? |
Schedule is here: https://apireview.net/ - if you offer help I can bump milestone to 8.0 to increase priority |
Should we also have a |
It's an option. Seems like it could be ambiguous on what the behavior would be though; e.g. would it still enqueue if there was nothing to dequeue first? Also, unlike the DequeueEnqueue signature, I wouldn't expect a significant performance difference with checking using a sequential call to TryPeek first.
Yes I can put together an implementation and tests PR. |
Ok, you're now somewhere in the middle of the list up for review (I'm guessing it will take at most couple of weeks), you can update the proposal with TryDequeEnqueue and similar, make sure to be consistent with remainder of the APIs |
Perhaps |
another option is to simply throw, if you're calling that API rather than just enqueue you most likely have bug in your app |
@RyanThomas73 for completeness, could you point out a couple of use cases/algorithms where using extract-then-insert is beneficial? |
This method seems redundant. A |
Hi Eric. Expanding on my background information/example, one common use case is when the heap/priority queue needs to be kept within a fixed initial size/max size and the additional elements added in as the next min is dequeued. public record ListNode(ListNode? Next, int Value);
ListNode producerOne = ... get producer data ...
ListNode producerTwo = ... get producer data ...
var initialElements = new List<(ListNode Element, int Priority)>();
// Limit the queue to the first 200 inputs from the producers
while (initialElements.Count <= 200
&& (producerOne is not null || producerTwo is not null))
{
// build initial elements, limited to the first 200 elements
}
var priorityQueue = new PriorityQueue<ListNode, int>(initialElements);
while (priorityQueue.TryPeek(out var peekedElement, out _))
{
// Each time we dequeue an element and have a next element to enqueue
// we want to use DequeueEnqueue which gives better performance than a Dequeue() followed by a sequential Enqueue()
var minElement = peekedElement.Next != null
? priorityQueue.DequeueEnqueue(peekedElement.Next, peekedElement.Next.Value)
: priorityQueue.Dequeue();
// ... consumer logic that uses minElement ....
} |
What if the contents of the |
Then a non-thread-safe collection is being mutated concurrently erroneously. |
@krwq @eiriktsarpalis I was hoping you could clarify the intention of this benchmark: Do you know if it is/was intended to serve as a sequential operations comparison for the K_Max_Elements() benchmark? If so should the sequential operations benchmark be updated to have the same fixed size |
Please wait until and if the API proposal has been approved.
The two benchmarks are independent and aren't intended for direct comparison. The purpose of these benchmarks is detecting possible performance regressions, so we generally try to avoid changing them once they have been added. |
namespace System.Collections.Generic;
public class PriorityQueue<TElement, TPriority>
{
public TElement DequeueEnqueue(TElement element, TPriority priority);
} |
@RyanThomas73 API has been approved as proposed, feel free to open a PR contributing the implementation. |
…tions.Generic.PriorityQueue public api and unit test cases for the new method
…property test case and update DequeueEnqueue test assertion and xunit attribute
…tyQueue.PropertyTests.cs test case method names
* Issue #75070 - Add a DequeueEnqueue method to the System.Collections.Generic.PriorityQueue public api and unit test cases for the new method * Issue #75070 - Remove unnecessary PriorityQueue DequeueEnqueue property test case and update DequeueEnqueue test assertion and xunit attribute * Item #75070 - Revert the unnecessary name changes to the PriorityQueue.PropertyTests.cs test case method names
Background and motivation
When working with d-ary heap structures, insert-then-extract and extract-then-insert operations can be done which are generally more efficient than sequentially executing the separate insert/extract operations.
The existing
PriorityQueue<TElement, TPriority>
collection type has an existing method signature namedEnqueueDequeue
which is an optimized implementation of the insert-then-extract operation. It does not have an api method for the inverse extract-then-insert operation.The term pop-push is sometimes used for the extract-then-insert implementation. The python implementation calls this
heapreplace
: https://docs.python.org/3/library/heapq.html#heapq.heapreplaceThe optimized extract-then-insert operation is ideal for use cases where each dequeue operation is likely or guaranteed to be followed by an enqueue operation.
One example usage of the new method would be a scenario where the elements of the priority queue represent nodes from different linked lists. If each operation to dequeue the next element is always followed by inserting the next node that is pointed at, the optimized extract-then-insert would be the preferable choice.
API Proposal
API Usage
Alternative Designs
N/A
Risks
The new method implementation must be correctly optimized in order to achieve better performance than executing the Dequeue() and Enqueue(..) methods sequentially.
The text was updated successfully, but these errors were encountered: