using System;
using System.Collections;
using System.Collections.Generic;
namespace Priority_Queue
{
///
/// A simplified priority queue implementation. Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than
/// FastPriorityQueue
/// Methods tagged as O(1) or O(log n) are assuming there are no duplicates. Duplicates may increase the algorithmic complexity.
///
/// The type to enqueue
/// The priority-type to use for nodes. Must extend IComparable<TPriority>
public class SimplePriorityQueue : IPriorityQueue
where TPriority : IComparable
{
private class SimpleNode : GenericPriorityQueueNode
{
public TItem Data { get; private set; }
public SimpleNode(TItem data)
{
Data = data;
}
}
private const int INITIAL_QUEUE_SIZE = 10;
private readonly GenericPriorityQueue _queue;
private readonly Dictionary> _itemToNodesCache;
private readonly IList _nullNodesCache;
#region Constructors
///
/// Instantiate a new Priority Queue
///
public SimplePriorityQueue() : this(Comparer.Default, EqualityComparer.Default) { }
///
/// Instantiate a new Priority Queue
///
/// The comparer used to compare TPriority values. Defaults to Comparer<TPriority>.default
public SimplePriorityQueue(IComparer priorityComparer) : this(priorityComparer.Compare, EqualityComparer.Default) { }
///
/// Instantiate a new Priority Queue
///
/// The comparison function to use to compare TPriority values
public SimplePriorityQueue(Comparison priorityComparer) : this(priorityComparer, EqualityComparer.Default) { }
///
/// Instantiate a new Priority Queue
///
/// The equality comparison function to use to compare TItem values
public SimplePriorityQueue(IEqualityComparer itemEquality) : this(Comparer.Default, itemEquality) { }
///
/// Instantiate a new Priority Queue
///
/// The comparer used to compare TPriority values. Defaults to Comparer<TPriority>.default
/// The equality comparison function to use to compare TItem values
public SimplePriorityQueue(IComparer priorityComparer, IEqualityComparer itemEquality) : this(priorityComparer.Compare, itemEquality) { }
///
/// Instantiate a new Priority Queue
///
/// The comparison function to use to compare TPriority values
/// The equality comparison function to use to compare TItem values
public SimplePriorityQueue(Comparison priorityComparer, IEqualityComparer itemEquality)
{
_queue = new GenericPriorityQueue(INITIAL_QUEUE_SIZE, priorityComparer);
_itemToNodesCache = new Dictionary>(itemEquality);
_nullNodesCache = new List();
}
#endregion
///
/// Given an item of type T, returns the existing SimpleNode in the queue
///
private SimpleNode GetExistingNode(TItem item)
{
if (item == null)
{
return _nullNodesCache.Count > 0 ? _nullNodesCache[0] : null;
}
IList nodes;
if (!_itemToNodesCache.TryGetValue(item, out nodes))
{
return null;
}
return nodes[0];
}
///
/// Adds an item to the Node-cache to allow for many methods to be O(1) or O(log n)
///
private void AddToNodeCache(SimpleNode node)
{
if (node.Data == null)
{
_nullNodesCache.Add(node);
return;
}
IList nodes;
if (!_itemToNodesCache.TryGetValue(node.Data, out nodes))
{
nodes = new List();
_itemToNodesCache[node.Data] = nodes;
}
nodes.Add(node);
}
///
/// Removes an item to the Node-cache to allow for many methods to be O(1) or O(log n) (assuming no duplicates)
///
private void RemoveFromNodeCache(SimpleNode node)
{
if (node.Data == null)
{
_nullNodesCache.Remove(node);
return;
}
IList nodes;
if (!_itemToNodesCache.TryGetValue(node.Data, out nodes))
{
return;
}
nodes.Remove(node);
if (nodes.Count == 0)
{
_itemToNodesCache.Remove(node.Data);
}
}
///
/// Returns the number of nodes in the queue.
/// O(1)
///
public int Count
{
get
{
lock(_queue)
{
return _queue.Count;
}
}
}
///
/// Returns the head of the queue, without removing it (use Dequeue() for that).
/// Throws an exception when the queue is empty.
/// O(1)
///
public TItem First
{
get
{
lock(_queue)
{
if(_queue.Count <= 0)
{
throw new InvalidOperationException("Cannot call .First on an empty queue");
}
return _queue.First.Data;
}
}
}
///
/// Removes every node from the queue.
/// O(n)
///
public void Clear()
{
lock(_queue)
{
_queue.Clear();
_itemToNodesCache.Clear();
_nullNodesCache.Clear();
}
}
///
/// Returns whether the given item is in the queue.
/// O(1)
///
public bool Contains(TItem item)
{
lock(_queue)
{
return item == null ? _nullNodesCache.Count > 0 : _itemToNodesCache.ContainsKey(item);
}
}
///
/// 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)
///
public TItem Dequeue()
{
lock(_queue)
{
if(_queue.Count <= 0)
{
throw new InvalidOperationException("Cannot call Dequeue() on an empty queue");
}
SimpleNode node =_queue.Dequeue();
RemoveFromNodeCache(node);
return node.Data;
}
}
///
/// Enqueue the item with the given priority, without calling lock(_queue) or AddToNodeCache(node)
///
///
///
///
private SimpleNode EnqueueNoLockOrCache(TItem item, TPriority priority)
{
SimpleNode node = new SimpleNode(item);
if (_queue.Count == _queue.MaxSize)
{
_queue.Resize(_queue.MaxSize * 2 + 1);
}
_queue.Enqueue(node, priority);
return node;
}
///
/// 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 and null-values are allowed.
/// O(log n)
///
public void Enqueue(TItem item, TPriority priority)
{
lock(_queue)
{
IList nodes;
if (item == null)
{
nodes = _nullNodesCache;
}
else if (!_itemToNodesCache.TryGetValue(item, out nodes))
{
nodes = new List();
_itemToNodesCache[item] = nodes;
}
SimpleNode node = EnqueueNoLockOrCache(item, priority);
nodes.Add(node);
}
}
///
/// Enqueue a node to the priority queue if it doesn't already exist. 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'. Null values are allowed.
/// Returns true if the node was successfully enqueued; false if it already exists.
/// O(log n)
///
public bool EnqueueWithoutDuplicates(TItem item, TPriority priority)
{
lock(_queue)
{
IList nodes;
if (item == null)
{
if (_nullNodesCache.Count > 0)
{
return false;
}
nodes = _nullNodesCache;
}
else if (_itemToNodesCache.ContainsKey(item))
{
return false;
}
else
{
nodes = new List();
_itemToNodesCache[item] = nodes;
}
SimpleNode node = EnqueueNoLockOrCache(item, priority);
nodes.Add(node);
return true;
}
}
///
/// 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(log n)
///
public void Remove(TItem item)
{
lock(_queue)
{
SimpleNode removeMe;
IList nodes;
if (item == null)
{
if (_nullNodesCache.Count == 0)
{
throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item);
}
removeMe = _nullNodesCache[0];
nodes = _nullNodesCache;
}
else
{
if (!_itemToNodesCache.TryGetValue(item, out nodes))
{
throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item);
}
removeMe = nodes[0];
if (nodes.Count == 1)
{
_itemToNodesCache.Remove(item);
}
}
_queue.Remove(removeMe);
nodes.Remove(removeMe);
}
}
///
/// 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 and be able
/// to update all of them, please wrap your items in a wrapper class so they can be distinguished).
/// O(log n)
///
public void UpdatePriority(TItem item, TPriority priority)
{
lock (_queue)
{
SimpleNode updateMe = GetExistingNode(item);
if (updateMe == null)
{
throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + item);
}
_queue.UpdatePriority(updateMe, priority);
}
}
///
/// Returns the priority of the given item.
/// Calling this method on a item not in the queue will throw an exception.
/// If the item is enqueued multiple times, only the priority of the first will be returned.
/// (If your requirements are complex enough that you need to enqueue the same item multiple times and be able
/// to query all their priorities, please wrap your items in a wrapper class so they can be distinguished).
/// O(1)
///
public TPriority GetPriority(TItem item)
{
lock (_queue)
{
SimpleNode findMe = GetExistingNode(item);
if(findMe == null)
{
throw new InvalidOperationException("Cannot call GetPriority() on a node which is not enqueued: " + item);
}
return findMe.Priority;
}
}
#region Try* methods for multithreading
/// Get the head of the queue, without removing it (use TryDequeue() for that).
/// Useful for multi-threading, where the queue may become empty between calls to Contains() and First
/// Returns true if successful, false otherwise
/// O(1)
public bool TryFirst(out TItem first)
{
if (_queue.Count > 0)
{
lock (_queue)
{
if (_queue.Count > 0)
{
first = _queue.First.Data;
return true;
}
}
}
first = default(TItem);
return false;
}
///
/// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and sets it to first.
/// Useful for multi-threading, where the queue may become empty between calls to Contains() and Dequeue()
/// Returns true if successful; false if queue was empty
/// O(log n)
///
public bool TryDequeue(out TItem first)
{
if (_queue.Count > 0)
{
lock (_queue)
{
if (_queue.Count > 0)
{
SimpleNode node = _queue.Dequeue();
first = node.Data;
RemoveFromNodeCache(node);
return true;
}
}
}
first = default(TItem);
return false;
}
///
/// Attempts to remove an item from the queue. The item does not need to be the head of the queue.
/// Useful for multi-threading, where the queue may become empty between calls to Contains() and Remove()
/// Returns true if the item was successfully removed, false if it wasn't in the queue.
/// If multiple copies of the item are enqueued, only the first one is removed.
/// O(log n)
///
public bool TryRemove(TItem item)
{
lock(_queue)
{
SimpleNode removeMe;
IList nodes;
if (item == null)
{
if (_nullNodesCache.Count == 0)
{
return false;
}
removeMe = _nullNodesCache[0];
nodes = _nullNodesCache;
}
else
{
if (!_itemToNodesCache.TryGetValue(item, out nodes))
{
return false;
}
removeMe = nodes[0];
if (nodes.Count == 1)
{
_itemToNodesCache.Remove(item);
}
}
_queue.Remove(removeMe);
nodes.Remove(removeMe);
return true;
}
}
///
/// Call this method to change the priority of an item.
/// Useful for multi-threading, where the queue may become empty between calls to Contains() and UpdatePriority()
/// 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 and be able
/// to update all of them, please wrap your items in a wrapper class so they can be distinguished).
/// Returns true if the item priority was updated, false otherwise.
/// O(log n)
///
public bool TryUpdatePriority(TItem item, TPriority priority)
{
lock(_queue)
{
SimpleNode updateMe = GetExistingNode(item);
if(updateMe == null)
{
return false;
}
_queue.UpdatePriority(updateMe, priority);
return true;
}
}
///
/// Attempt to get the priority of the given item.
/// Useful for multi-threading, where the queue may become empty between calls to Contains() and GetPriority()
/// If the item is enqueued multiple times, only the priority of the first will be returned.
/// (If your requirements are complex enough that you need to enqueue the same item multiple times and be able
/// to query all their priorities, please wrap your items in a wrapper class so they can be distinguished).
/// Returns true if the item was found in the queue, false otherwise
/// O(1)
///
public bool TryGetPriority(TItem item, out TPriority priority)
{
lock(_queue)
{
SimpleNode findMe = GetExistingNode(item);
if(findMe == null)
{
priority = default(TPriority);
return false;
}
priority = findMe.Priority;
return true;
}
}
#endregion
public IEnumerator GetEnumerator()
{
List queueData = new List();
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)
{
// Check all items in cache are in the queue
foreach (IList nodes in _itemToNodesCache.Values)
{
foreach (SimpleNode node in nodes)
{
if (!_queue.Contains(node))
{
return false;
}
}
}
// Check all items in queue are in cache
foreach (SimpleNode node in _queue)
{
if (GetExistingNode(node.Data) == null)
{
return false;
}
}
// Check queue structure itself
return _queue.IsValidQueue();
}
}
}
///
/// 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 SimplePriorityQueue<TItem, TPriority>
///
/// The type to enqueue
public class SimplePriorityQueue : SimplePriorityQueue
{
///
/// Instantiate a new Priority Queue
///
public SimplePriorityQueue() { }
///
/// Instantiate a new Priority Queue
///
/// The comparer used to compare priority values. Defaults to Comparer<float>.default
public SimplePriorityQueue(IComparer comparer) : base(comparer) { }
///
/// Instantiate a new Priority Queue
///
/// The comparison function to use to compare priority values
public SimplePriorityQueue(Comparison comparer) : base(comparer) { }
}
}