//-------------------------------------------------------------------------- // // 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; } } } } }