using System; using System.Collections; using System.Collections.Generic; namespace Priority_Queue { /// <summary> /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp /// A simplified priority queue implementation. Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than /// FastPriorityQueue /// </summary> /// <typeparam name="TItem">The type to enqueue</typeparam> /// <typeparam name="TPriority">The priority-type to use for nodes. Must extend IComparable<TPriority></typeparam> public class SimplePriorityQueue<TItem, TPriority> : IPriorityQueue<TItem, TPriority> where TPriority : IComparable<TPriority> { private class SimpleNode : GenericPriorityQueueNode<TPriority> { public TItem Data { get; private set; } public SimpleNode(TItem data) { Data = data; } } private const int INITIAL_QUEUE_SIZE = 10; private readonly GenericPriorityQueue<SimpleNode, TPriority> _queue; public SimplePriorityQueue() { _queue = new GenericPriorityQueue<SimpleNode, TPriority>(INITIAL_QUEUE_SIZE); } /// <summary> /// Given an item of type T, returns the exist SimpleNode in the queue /// </summary> private SimpleNode GetExistingNode(TItem item) { var comparer = EqualityComparer<TItem>.Default; foreach (var node in _queue) { if (comparer.Equals(node.Data, item)) { return node; } } throw new InvalidOperationException("Item cannot be found in queue: " + item); } /// <summary> /// Returns the number of nodes in the queue. /// O(1) /// </summary> public int Count { get { lock (_queue) { return _queue.Count; } } } /// <summary> /// Returns the head of the queue, without removing it (use Dequeue() for that). /// Throws an exception when the queue is empty. /// O(1) /// </summary> public TItem First { get { lock (_queue) { if (_queue.Count <= 0) { throw new InvalidOperationException("Cannot call .First on an empty queue"); } SimpleNode first = _queue.First; return (first != null ? first.Data : default(TItem)); } } } /// <summary> /// Removes every node from the queue. /// O(n) /// </summary> public void Clear() { lock (_queue) { _queue.Clear(); } } /// <summary> /// Returns whether the given item is in the queue. /// O(n) /// </summary> public bool Contains(TItem item) { lock (_queue) { var comparer = EqualityComparer<TItem>.Default; foreach (var node in _queue) { if (comparer.Equals(node.Data, item)) { return true; } } return false; } } /// <summary> /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. /// If queue is empty, throws an exception /// O(log n) /// </summary> public bool TryDequeue(out TItem item) { lock (_queue) { if (_queue.Count <= 0) { item = default(TItem); return false; } if (_queue.TryDequeue(out SimpleNode node)) { item = node.Data; return true; } item = default(TItem); return false; } } /// <summary> /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'. /// Duplicates are allowed. /// O(log n) /// </summary> public void Enqueue(TItem item, TPriority priority) { lock (_queue) { var node = new SimpleNode(item); if (_queue.Count == _queue.MaxSize) { _queue.Resize(_queue.MaxSize * 2 + 1); } _queue.Enqueue(node, priority); } } /// <summary> /// Removes an item from the queue. The item does not need to be the head of the queue. /// If the item is not in the queue, an exception is thrown. If unsure, check Contains() first. /// If multiple copies of the item are enqueued, only the first one is removed. /// O(n) /// </summary> public void Remove(TItem item) { lock (_queue) { try { _queue.Remove(GetExistingNode(item)); } catch (InvalidOperationException ex) { throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item, ex); } } } /// <summary> /// Call this method to change the priority of an item. /// Calling this method on a item not in the queue will throw an exception. /// If the item is enqueued multiple times, only the first one will be updated. /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able /// to update all of them, please wrap your items in a wrapper class so they can be distinguished). /// O(n) /// </summary> public void UpdatePriority(TItem item, TPriority priority) { lock (_queue) { try { SimpleNode updateMe = GetExistingNode(item); _queue.UpdatePriority(updateMe, priority); } catch (InvalidOperationException ex) { throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + item, ex); } } } public IEnumerator<TItem> GetEnumerator() { var queueData = new List<TItem>(); lock (_queue) { //Copy to a separate list because we don't want to 'yield return' inside a lock foreach (var node in _queue) { queueData.Add(node.Data); } } return queueData.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public bool IsValidQueue() { lock (_queue) { return _queue.IsValidQueue(); } } } /// <summary> /// A simplified priority queue implementation. Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than /// FastPriorityQueue /// This class is kept here for backwards compatibility. It's recommended you use Simple /// </summary> /// <typeparam name="TItem">The type to enqueue</typeparam> public class SimplePriorityQueue<TItem> : SimplePriorityQueue<TItem, float> { } }