jellyfin-server/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs
2019-01-17 20:24:39 +01:00

248 lines
7.9 KiB
C#

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&lt;TPriority&gt;</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> { }
}