//--------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: ConcurrentPriorityQueue.cs
//
//--------------------------------------------------------------------------
using System.Collections.Generic;
using System.Diagnostics;
namespace System.Collections.Concurrent
{
/// Provides a thread-safe priority queue data structure.
/// Specifies the type of keys used to prioritize values.
/// Specifies the type of elements in the queue.
[DebuggerDisplay("Count={Count}")]
public class ConcurrentPriorityQueue :
IProducerConsumerCollection>
where TKey : IComparable
{
private readonly object _syncLock = new object();
private readonly MinBinaryHeap _minHeap = new MinBinaryHeap();
/// Initializes a new instance of the ConcurrentPriorityQueue class.
public ConcurrentPriorityQueue() {}
/// Initializes a new instance of the ConcurrentPriorityQueue class that contains elements copied from the specified collection.
/// The collection whose elements are copied to the new ConcurrentPriorityQueue.
public ConcurrentPriorityQueue(IEnumerable> collection)
{
if (collection == null) throw new ArgumentNullException("collection");
foreach (var item in collection) _minHeap.Insert(item);
}
/// Adds the key/value pair to the priority queue.
/// The priority of the item to be added.
/// The item to be added.
public void Enqueue(TKey priority, TValue value)
{
Enqueue(new KeyValuePair(priority, value));
}
/// Adds the key/value pair to the priority queue.
/// The key/value pair to be added to the queue.
public void Enqueue(KeyValuePair item)
{
lock (_syncLock) _minHeap.Insert(item);
}
/// Attempts to remove and return the next prioritized item in the queue.
///
/// When this method returns, if the operation was successful, result contains the object removed. If
/// no object was available to be removed, the value is unspecified.
///
///
/// true if an element was removed and returned from the queue succesfully; otherwise, false.
///
public bool TryDequeue(out KeyValuePair result)
{
result = default(KeyValuePair);
lock (_syncLock)
{
if (_minHeap.Count > 0)
{
result = _minHeap.Remove();
return true;
}
}
return false;
}
/// Attempts to return the next prioritized item in the queue.
///
/// When this method returns, if the operation was successful, result contains the object.
/// The queue was not modified by the operation.
///
///
/// true if an element was returned from the queue succesfully; otherwise, false.
///
public bool TryPeek(out KeyValuePair result)
{
result = default(KeyValuePair);
lock (_syncLock)
{
if (_minHeap.Count > 0)
{
result = _minHeap.Peek();
return true;
}
}
return false;
}
/// Empties the queue.
public void Clear() { lock(_syncLock) _minHeap.Clear(); }
/// Gets whether the queue is empty.
public bool IsEmpty { get { return Count == 0; } }
/// Gets the number of elements contained in the queue.
public int Count
{
get { lock (_syncLock) return _minHeap.Count; }
}
/// Copies the elements of the collection to an array, starting at a particular array index.
///
/// The one-dimensional array that is the destination of the elements copied from the queue.
///
///
/// The zero-based index in array at which copying begins.
///
/// The elements will not be copied to the array in any guaranteed order.
public void CopyTo(KeyValuePair[] array, int index)
{
lock (_syncLock) _minHeap.Items.CopyTo(array, index);
}
/// Copies the elements stored in the queue to a new array.
/// A new array containing a snapshot of elements copied from the queue.
public KeyValuePair[] ToArray()
{
lock (_syncLock)
{
var clonedHeap = new MinBinaryHeap(_minHeap);
var result = new KeyValuePair[_minHeap.Count];
for (int i = 0; i < result.Length; i++)
{
result[i] = clonedHeap.Remove();
}
return result;
}
}
/// Attempts to add an item in the queue.
/// The key/value pair to be added.
///
/// true if the pair was added; otherwise, false.
///
bool IProducerConsumerCollection>.TryAdd(KeyValuePair item)
{
Enqueue(item);
return true;
}
/// Attempts to remove and return the next prioritized item in the queue.
///
/// When this method returns, if the operation was successful, result contains the object removed. If
/// no object was available to be removed, the value is unspecified.
///
///
/// true if an element was removed and returned from the queue succesfully; otherwise, false.
///
bool IProducerConsumerCollection>.TryTake(out KeyValuePair item)
{
return TryDequeue(out item);
}
/// Returns an enumerator that iterates through the collection.
/// An enumerator for the contents of the queue.
///
/// The enumeration represents a moment-in-time snapshot of the contents of the queue. It does not
/// reflect any updates to the collection after GetEnumerator was called. The enumerator is safe to
/// use concurrently with reads from and writes to the queue.
///
public IEnumerator> GetEnumerator()
{
var arr = ToArray();
return ((IEnumerable>)arr).GetEnumerator();
}
/// Returns an enumerator that iterates through a collection.
/// An IEnumerator that can be used to iterate through the collection.
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
/// Copies the elements of the collection to an array, starting at a particular array index.
///
/// The one-dimensional array that is the destination of the elements copied from the queue.
///
///
/// The zero-based index in array at which copying begins.
///
void ICollection.CopyTo(Array array, int index)
{
lock (_syncLock) ((ICollection)_minHeap.Items).CopyTo(array, index);
}
///
/// Gets a value indicating whether access to the ICollection is synchronized with the SyncRoot.
///
bool ICollection.IsSynchronized { get { return true; } }
///
/// Gets an object that can be used to synchronize access to the collection.
///
object ICollection.SyncRoot { get { return _syncLock; } }
/// Implements a binary heap that prioritizes smaller values.
private sealed class MinBinaryHeap
{
private readonly List> _items;
/// Initializes an empty heap.
public MinBinaryHeap()
{
_items = new List>();
}
/// Initializes a heap as a copy of another heap instance.
/// The heap to copy.
/// Key/Value values are not deep cloned.
public MinBinaryHeap(MinBinaryHeap heapToCopy)
{
_items = new List>(heapToCopy.Items);
}
/// Empties the heap.
public void Clear() { _items.Clear(); }
/// Adds an item to the heap.
public void Insert(TKey key, TValue value)
{
// Create the entry based on the provided key and value
Insert(new KeyValuePair(key, value));
}
/// Adds an item to the heap.
public void Insert(KeyValuePair entry)
{
// Add the item to the list, making sure to keep track of where it was added.
_items.Add(entry);
int pos = _items.Count - 1;
// If the new item is the only item, we're done.
if (pos == 0) return;
// Otherwise, perform log(n) operations, walking up the tree, swapping
// where necessary based on key values
while (pos > 0)
{
// Get the next position to check
int nextPos = pos / 2;
// Extract the entry at the next position
var toCheck = _items[nextPos];
// Compare that entry to our new one. If our entry has a smaller key, move it up.
// Otherwise, we're done.
if (entry.Key.CompareTo(toCheck.Key) < 0)
{
_items[pos] = toCheck;
pos = nextPos;
}
else break;
}
// Make sure we put this entry back in, just in case
_items[pos] = entry;
}
/// Returns the entry at the top of the heap.
public KeyValuePair Peek()
{
// Returns the first item
if (_items.Count == 0) throw new InvalidOperationException("The heap is empty.");
return _items[0];
}
/// Removes the entry at the top of the heap.
public KeyValuePair Remove()
{
// Get the first item and save it for later (this is what will be returned).
if (_items.Count == 0) throw new InvalidOperationException("The heap is empty.");
KeyValuePair toReturn = _items[0];
// Remove the first item if there will only be 0 or 1 items left after doing so.
if (_items.Count <= 2) _items.RemoveAt(0);
// A reheapify will be required for the removal
else
{
// Remove the first item and move the last item to the front.
_items[0] = _items[_items.Count - 1];
_items.RemoveAt(_items.Count - 1);
// Start reheapify
int current = 0, possibleSwap = 0;
// Keep going until the tree is a heap
while (true)
{
// Get the positions of the node's children
int leftChildPos = 2 * current + 1;
int rightChildPos = leftChildPos + 1;
// Should we swap with the left child?
if (leftChildPos < _items.Count)
{
// Get the two entries to compare (node and its left child)
var entry1 = _items[current];
var entry2 = _items[leftChildPos];
// If the child has a lower key than the parent, set that as a possible swap
if (entry2.Key.CompareTo(entry1.Key) < 0) possibleSwap = leftChildPos;
}
else break; // if can't swap this, we're done
// Should we swap with the right child? Note that now we check with the possible swap
// position (which might be current and might be left child).
if (rightChildPos < _items.Count)
{
// Get the two entries to compare (node and its left child)
var entry1 = _items[possibleSwap];
var entry2 = _items[rightChildPos];
// If the child has a lower key than the parent, set that as a possible swap
if (entry2.Key.CompareTo(entry1.Key) < 0) possibleSwap = rightChildPos;
}
// Now swap current and possible swap if necessary
if (current != possibleSwap)
{
var temp = _items[current];
_items[current] = _items[possibleSwap];
_items[possibleSwap] = temp;
}
else break; // if nothing to swap, we're done
// Update current to the location of the swap
current = possibleSwap;
}
}
// Return the item from the heap
return toReturn;
}
/// Gets the number of objects stored in the heap.
public int Count { get { return _items.Count; } }
internal List> Items { get { return _items; } }
}
}
}