Last active
March 29, 2018 16:28
-
-
Save pmunin/affc23dd89950e67ece9ca3b19b9508a to your computer and use it in GitHub Desktop.
Simple .NET PriorityQueue based on SortedDictionary<TKey, Queue<TItem>> (BST) behind the scene
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
| //Latest version is here: https://gist.github.com/affc23dd89950e67ece9ca3b19b9508a.git | |
| using System; | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| namespace PriorityQueueUtils | |
| { | |
| /// <summary> | |
| /// Queue contract | |
| /// </summary> | |
| /// <typeparam name="T"></typeparam> | |
| public interface IQueue<T>: IEnumerable<T> | |
| { | |
| /// <summary> | |
| /// Add item to queue | |
| /// </summary> | |
| /// <param name="item"></param> | |
| void Enqueue(T item); | |
| /// <summary> | |
| /// Add item to queue | |
| /// </summary> | |
| /// <param name="item"></param> | |
| void EnqueueRange(IEnumerable<T> items); | |
| /// <summary> | |
| /// Remove top item from queue | |
| /// </summary> | |
| /// <returns></returns> | |
| T Dequeue(); | |
| /// <summary> | |
| /// Check if queue is empty | |
| /// </summary> | |
| bool IsEmpty { get; } | |
| /// <summary> | |
| /// Get item on the top of the queue, without removing it from the queue | |
| /// </summary> | |
| /// <returns></returns> | |
| T Peek(); | |
| /// <summary> | |
| /// Check if the queue contains specified item | |
| /// </summary> | |
| /// <param name="item"></param> | |
| /// <returns></returns> | |
| bool Contains(T item); | |
| } | |
| /// <summary> | |
| /// Double ended queue, allowing to dequeue from top and bottom of the queue | |
| /// </summary> | |
| /// <typeparam name="T"></typeparam> | |
| public interface IQueueDoubleEnded<T>: IQueue<T> | |
| { | |
| /// <summary> | |
| /// Dequeue from the bottom of the queue | |
| /// </summary> | |
| /// <returns></returns> | |
| T DequeueLast(); | |
| /// <summary> | |
| /// Get the last item of the queue | |
| /// </summary> | |
| /// <returns></returns> | |
| T PeekLast(); | |
| } | |
| /// <summary> | |
| /// Simple key value based Min Heap priority queue. | |
| /// Uses SortedDictionary and its BST behind the scene. Unlike SortedDictionary it Allow duplicate values. | |
| /// </summary> | |
| /// <typeparam name="TItem"></typeparam> | |
| /// <typeparam name="TPriority"></typeparam> | |
| public class PriorityQueue<TItem, TPriority> : IQueueDoubleEnded<TItem>, IEnumerable<TItem> | |
| { | |
| public PriorityQueue(Func<TItem, TPriority> getPriority, IEnumerable<TItem> items = null, IComparer<TPriority> comparer = null) | |
| { | |
| this.getPriority = getPriority; | |
| var tupleComparer = comparer != null ? new TupleComparer<TPriority>(comparer) : null; | |
| binaryTree = new SortedDictionary<Tuple<TPriority>, Queue<TItem>>(tupleComparer); | |
| AddRange(items); | |
| } | |
| /// <summary> | |
| /// Add multiple items to the queue | |
| /// </summary> | |
| /// <param name="items"></param> | |
| public void AddRange(IEnumerable<TItem> items) | |
| { | |
| foreach (var item in items ?? Enumerable.Empty<TItem>()) | |
| Enqueue(item); | |
| } | |
| Func<TItem, TPriority> getPriority; | |
| /// <summary> | |
| /// Dictionary prohibit using Null as key, that's why we have to create Tuple | |
| /// </summary> | |
| SortedDictionary<Tuple<TPriority>, Queue<TItem>> binaryTree; | |
| public IEnumerable<TItem> GetItems() | |
| { | |
| foreach (var kvp in binaryTree) | |
| foreach (var item in kvp.Value) | |
| yield return item; | |
| } | |
| public bool IsEmpty | |
| { | |
| get | |
| { | |
| return binaryTree.Count == 0; | |
| } | |
| } | |
| public TItem Dequeue() | |
| { | |
| var firstKvp = binaryTree.First(); | |
| var res = firstKvp.Value.Dequeue(); | |
| if (firstKvp.Value.Count == 0) | |
| if (!binaryTree.Remove(firstKvp.Key)) | |
| { | |
| //throw new InvalidOperationException(); | |
| } | |
| return res; | |
| } | |
| Tuple<TPriority> T(TPriority priority) | |
| { | |
| //TODO: pooling to save memory | |
| return Tuple.Create(priority); | |
| } | |
| public void Enqueue(TItem item) | |
| { | |
| Queue<TItem> queue = null; | |
| var key = T(getPriority(item)); | |
| if (!binaryTree.TryGetValue(key, out queue)) | |
| binaryTree[key] = queue = new Queue<TItem>(); | |
| queue.Enqueue(item); | |
| } | |
| IEnumerator<TItem> IEnumerable<TItem>.GetEnumerator() | |
| { | |
| return this.GetItems().GetEnumerator(); | |
| } | |
| IEnumerator IEnumerable.GetEnumerator() | |
| { | |
| return this.GetItems().GetEnumerator(); | |
| } | |
| public TItem DequeueLast() | |
| { | |
| var kvp = binaryTree.Last(); | |
| var res = kvp.Value.Dequeue(); | |
| if (kvp.Value.Count == 0) | |
| binaryTree.Remove(kvp.Key); | |
| return res; | |
| } | |
| public TItem Peek() | |
| { | |
| var firstKvp = binaryTree.First(); | |
| var res = firstKvp.Value.Peek(); | |
| return res; | |
| } | |
| public TItem PeekLast() | |
| { | |
| var kvp = binaryTree.Last(); | |
| var res = kvp.Value.Peek(); | |
| return res; | |
| } | |
| public bool Contains(TItem item) | |
| { | |
| var itemPriority = T(getPriority(item)); | |
| Queue<TItem> items = null; | |
| if (!binaryTree.TryGetValue(itemPriority, out items)) | |
| return false; | |
| return items.Contains(item); | |
| } | |
| public void EnqueueRange(IEnumerable<TItem> items) | |
| { | |
| foreach (var item in items) | |
| { | |
| Enqueue(item); | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Required to support adding Null as a Key to dictionaries (Nullable Priority to PriorityQueue) | |
| /// </summary> | |
| /// <typeparam name="T"></typeparam> | |
| public class TupleComparer<T> : IComparer<Tuple<T>> | |
| { | |
| public TupleComparer(IComparer<T> source) | |
| { | |
| this.Source = source??Comparer<T>.Default; | |
| } | |
| public IComparer<T> Source { get; private set; } | |
| public int Compare(Tuple<T> x, Tuple<T> y) | |
| { | |
| return Source.Compare(x.Item1, y.Item1); | |
| } | |
| } | |
| /// <summary> | |
| /// Simple Min Heap priority queue. | |
| /// Uses SortedDictionary and its BST behind the scene. Unlike SortedDictionary it Allow duplicate values. | |
| /// </summary> | |
| /// <typeparam name="TItem"></typeparam> | |
| public class PriorityQueue<TItem> : PriorityQueue<TItem, TItem> | |
| { | |
| public PriorityQueue(IEnumerable<TItem> items = null, IComparer<TItem> comparer = null) : base(i => i, items, comparer) | |
| { | |
| } | |
| } | |
| /// <summary> | |
| /// Helper class for creating PriorityQueues | |
| /// </summary> | |
| public static class PriorityQueue | |
| { | |
| /// <summary> | |
| /// Create priority queue, which will be sorted by comparer | |
| /// </summary> | |
| /// <typeparam name="T"></typeparam> | |
| /// <param name="items"></param> | |
| /// <param name="comparer"></param> | |
| /// <returns></returns> | |
| public static PriorityQueue<T> Create<T>(IEnumerable<T> items = null, IComparer<T> comparer = null) | |
| { | |
| return new PriorityQueue<T>(items, comparer); | |
| } | |
| /// <summary> | |
| /// Create priority queue, that will be sorted by priority extracted from item using specified delegate function | |
| /// </summary> | |
| /// <typeparam name="T"></typeparam> | |
| /// <typeparam name="TPriority"></typeparam> | |
| /// <param name="items"></param> | |
| /// <param name="getPriority"></param> | |
| /// <param name="comparer"></param> | |
| /// <returns></returns> | |
| public static PriorityQueue<T, TPriority> Create<T, TPriority>(IEnumerable<T> items, Func<T, TPriority> getPriority, IComparer<TPriority> comparer = null) | |
| { | |
| return new PriorityQueue<T, TPriority>(getPriority, items, comparer); | |
| } | |
| } | |
| } |
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
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Text; | |
| using System.Threading.Tasks; | |
| using Xunit; | |
| namespace PriorityQueueUtils | |
| { | |
| public class PriorityQueueTests | |
| { | |
| [Fact()] | |
| public void TestNullKey() | |
| { | |
| var items = new List<(MyPriority Priority, string name)>() | |
| { | |
| (new MyPriority(1),"adasd") , | |
| (new MyPriority(3453),""), | |
| (null, "qweqwe") | |
| } ; | |
| var queue = PriorityQueue.Create(items, i => i.Priority, MyPriority.Comparer.Instance); | |
| while (!queue.IsEmpty) | |
| { | |
| var next = queue.Dequeue(); | |
| } | |
| } | |
| class MyPriority | |
| { | |
| public MyPriority(int priority) | |
| { | |
| this.Priority = priority; | |
| } | |
| public int Priority { get; set; } | |
| public class Comparer : Comparer<MyPriority> | |
| { | |
| public static Comparer Instance { get; } = new Comparer(); | |
| public override int Compare(MyPriority x, MyPriority y) | |
| { | |
| //if (x == null) return 0; | |
| //if (y == null) return 0; | |
| return (x?.Priority??0) - (y?.Priority??0); | |
| } | |
| } | |
| } | |
| } | |
| } |
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
| using System; | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using System.Text; | |
| namespace PriorityQueueUtils | |
| { | |
| public static class QueueWrappers | |
| { | |
| public static IQueue<T> AsQueue<T>(this Stack<T> lifo) | |
| { | |
| return new StackWrapper<T>(lifo); | |
| } | |
| public static IQueue<T> AsQueue<T>(this Queue<T> fifo) | |
| { | |
| return new QueueWrapper<T>(fifo); | |
| } | |
| } | |
| public class QueueWrapper<T> : IQueue<T> | |
| { | |
| public QueueWrapper(Queue<T> source) | |
| { | |
| this.source = source ?? new Queue<T>(); | |
| } | |
| Queue<T> source; | |
| public bool IsEmpty => source.Count == 0; | |
| public T Dequeue() | |
| { | |
| return source.Dequeue(); | |
| } | |
| public void Enqueue(T item) | |
| { | |
| source.Enqueue(item); | |
| } | |
| public IEnumerator<T> GetEnumerator() | |
| { | |
| return source.GetEnumerator(); | |
| } | |
| public T Peek() | |
| { | |
| return source.Peek(); | |
| } | |
| IEnumerator IEnumerable.GetEnumerator() | |
| { | |
| return GetEnumerator(); | |
| } | |
| public bool Contains(T item) | |
| { | |
| return source.Contains(item); | |
| } | |
| public void EnqueueRange(IEnumerable<T> items) | |
| { | |
| foreach (var item in items) | |
| { | |
| source.Enqueue(item); | |
| } | |
| } | |
| } | |
| public class StackWrapper<T> : IQueue<T> | |
| { | |
| private Stack<T> source; | |
| public StackWrapper(Stack<T> source) | |
| { | |
| this.source = source ?? new Stack<T>(); | |
| } | |
| public bool IsEmpty => source.Count == 0; | |
| public bool Contains(T item) | |
| { | |
| return source.Contains(item); | |
| } | |
| public T Dequeue() | |
| { | |
| return source.Pop(); | |
| } | |
| public void Enqueue(T item) | |
| { | |
| source.Push(item); | |
| } | |
| public void EnqueueRange(IEnumerable<T> items) | |
| { | |
| foreach (var item in items) | |
| { | |
| Enqueue(item); | |
| } | |
| } | |
| public IEnumerator<T> GetEnumerator() | |
| { | |
| return source.GetEnumerator(); | |
| } | |
| public T Peek() | |
| { | |
| return source.Peek(); | |
| } | |
| IEnumerator IEnumerable.GetEnumerator() | |
| { | |
| return GetEnumerator(); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment