Last active
July 27, 2023 08:44
-
-
Save beminee/8df31a6a90f033f4762283a66050f1cd to your computer and use it in GitHub Desktop.
C# Thread-Safe O(logn) Priority Queue implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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