Skip to content

Instantly share code, notes, and snippets.

@beminee
Last active July 27, 2023 08:44
Show Gist options
  • Save beminee/8df31a6a90f033f4762283a66050f1cd to your computer and use it in GitHub Desktop.
Save beminee/8df31a6a90f033f4762283a66050f1cd to your computer and use it in GitHub Desktop.
C# Thread-Safe O(logn) Priority Queue implementation
// Inspired from https://gist.github.com/khenidak/49cf6f5ac76b608c9e3b3fc86c86cec0
// Enqueue: O(log n), because we might need to "bubble up" the new element to its proper place in the heap.
// Dequeue: O(log n), because we remove the top element and then "bubble down" the swapped element to its proper place.
// Peek: O(1), since we're just returning the top element.
public class ThreadSafePriorityQueue<TItem, TPriority> where TItem : class where TPriority : IComparable<TPriority>
{
private IComparer<TPriority> comparer;
private List<KeyValuePair<TItem, TPriority>> list = new List<KeyValuePair<TItem, TPriority>>();
private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
// Default constructor uses the default comparer for TPriority
public ThreadSafePriorityQueue() : this(Comparer<TPriority>.Default) { }
// If a specific comparer is provided, use that instead
public ThreadSafePriorityQueue(IComparer<TPriority> comparer)
{
this.comparer = comparer;
}
public int Count
{
get
{
rwLock.EnterReadLock();
var count = list.Count;
rwLock.ExitReadLock();
return count;
}
}
// Peek at the highest-priority item without removing it
public TItem Peek()
{
rwLock.EnterReadLock();
TItem item = null;
if (list.Count > 0)
{
item = list[0].Key;
}
rwLock.ExitReadLock();
return item;
}
// Enqueue an item with the given priority
// Higher-priority items are dequeued before lower-priority items
public void Enqueue(TItem item, TPriority priority)
{
if (item == null)
throw new ArgumentNullException();
rwLock.EnterWriteLock();
list.Add(new KeyValuePair<TItem, TPriority>(item, priority));
var childIndex = list.Count - 1;
// Bubble up to the correct place
while (childIndex > 0)
{
var parentIndex = (childIndex - 1) / 2;
if (comparer.Compare(list[childIndex].Value, list[parentIndex].Value) >= 0)
break;
(list[childIndex], list[parentIndex]) = (list[parentIndex], list[childIndex]);
childIndex = parentIndex;
}
rwLock.ExitWriteLock();
}
// Dequeue the highest-priority item
// If two items have the same priority, they are dequeued in the order they were enqueued
public TItem Dequeue()
{
TItem item = null;
rwLock.EnterWriteLock();
if (list.Count > 0)
{
item = list[0].Key;
var lastIndex = list.Count - 1;
list[0] = list[lastIndex];
list.RemoveAt(lastIndex);
lastIndex--;
var parentIndex = 0;
// Bubble down to the correct place
while (true)
{
var childIndex = parentIndex * 2 + 1;
if (childIndex > lastIndex) break;
var rightChild = childIndex + 1;
if (rightChild <= lastIndex && comparer.Compare(list[rightChild].Value, list[childIndex].Value) < 0)
childIndex = rightChild;
if (comparer.Compare(list[parentIndex].Value, list[childIndex].Value) <= 0)
break;
(list[parentIndex], list[childIndex]) = (list[childIndex], list[parentIndex]);
parentIndex = childIndex;
}
}
rwLock.ExitWriteLock();
return item;
}
}
class Route
{
public string Destination { get; set; }
}
// Create a new priority queue
var pq = new ThreadSafePriorityQueue<Route, int>();
// Enqueue routes with priorities
pq.Enqueue(new Route { Destination = "Tokyo" }, 1);
pq.Enqueue(new Route { Destination = "Paris" }, 2);
pq.Enqueue(new Route { Destination = "London" }, 3);
pq.Enqueue(new Route { Destination = "New York" }, 4);
// Dequeue routes by priority
while (pq.Count > 0)
{
var route = pq.Dequeue();
Console.WriteLine(route.Destination);
}
/* Output:
Tokyo
Paris
London
New York
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment