I am aware that there are other ways of implementing a priority queue e.g. using a sorted array/list. Would appreciate some comments on my implementation of a basic priority queue supporting three simple operations: Insert, Min, Max
public class QueueEntry<TKey, TValue> : IComparable<QueueEntry<TKey, TValue>> where TKey : IComparable<TKey>
{
public QueueEntry(TKey key, TValue value)
{
Key = key;
Value = value;
}
public TKey Key { get; }
public TValue Value { get; }
public int CompareTo(QueueEntry<TKey, TValue> other)
{
if (EqualityComparer<QueueEntry<TKey, TValue>>.Default.Equals(this, other)) return 0;
if (other == null) throw new ArgumentNullException();
return Key.CompareTo(other.Key);
}
public override string ToString() => $"Key = {Key}, Value = {Value}";
}
public sealed class PriorityQueue<TKey, TValue> where TKey : IComparable<TKey>
{
private List<QueueEntry<TKey, TValue>> items;
private TKey minKey;
private TKey maxKey;
private int minIndex;
private int maxIndex;
public PriorityQueue(int capacity)
{
items = new List<QueueEntry<TKey, TValue>>(capacity);
minKey = default(TKey);
maxKey = default(TKey);
minIndex = maxIndex = 0;
}
public PriorityQueue() : this(10)
{
}
public void Insert(QueueEntry<TKey, TValue> entry)
{
if (entry == null)
throw new ArgumentNullException(nameof(entry));
var key = entry.Key;
if (items.Count == 0) // first item
{
minKey = key;
maxKey = key;
}
else if (key.CompareTo(minKey) < 0) // a new min key
{
minKey = key;
minIndex = items.Count; // after the insert there will be one more entry.
}
else if (key.CompareTo(maxKey) > 0) // a new max key
{
maxKey = key;
maxIndex = items.Count;
}
items.Add(entry);
}
public int Count => items.Count;
public bool IsEmpty => items.Count == 0;
//Delete the entry with the min key; O(n).
public void DeleteMin()
{
if (IsEmpty) throw new InvalidOperationException("Queue is empty");
items.Remove(items[minIndex]);
if (!IsEmpty)
{
minIndex = 0;
for (var i = 1; i < items.Count; i++)
{
if (items[i].Key.CompareTo(items[minIndex].Key) < 0)
minIndex = i;
}
minKey = items[minIndex].Key;
}
}
//Delete the entry with the max key; O(n).
public void DeleteMax()
{
if (IsEmpty) throw new InvalidOperationException("Queue is empty");
items.Remove(items[maxIndex]);
if (!IsEmpty)
{
maxIndex = 0;
for (var i = 0; i < items.Count; i++)
{
if (items[i].Key.CompareTo(items[maxIndex].Key) > 1)
maxIndex = i;
}
maxKey = items[maxIndex].Key;
}
}
//O(1) to get min and max of the queue!
public QueueEntry<TKey, TValue> Min => IsEmpty ? null : items[minIndex];
public QueueEntry<TKey, TValue> Max => IsEmpty ? null : items[maxIndex];
}
These are some tests using NUnit3:
[TestFixture]
class PriorityQueueTest
{
[Test]
public void EmptyQueue_IsEmpty_ReturnTrue()
{
var queue = new PriorityQueue<int, string>();
Assert.IsTrue(queue.IsEmpty);
}
[Test]
public void AfterInsert_IsEmpty_ReturnFalse()
{
var queue = new PriorityQueue<int, string>();
queue.Insert(new QueueEntry<int, string>(1, "item1"));
Assert.IsFalse(queue.IsEmpty);
}
[Test]
public void FindMin_Return_CorrectMin()
{
var queue = new PriorityQueue<int, string>();
queue.Insert(new QueueEntry<int, string>(3, "item2"));
var minItem = new QueueEntry<int, string>(1, "min");
queue.Insert(minItem);
queue.Insert(new QueueEntry<int, string>(5, "item3"));
Assert.AreEqual(minItem, queue.Min);
}
[Test]
public void DeleteMin_Once_Return_CorrectMin()
{
var queue = new PriorityQueue<int, string>();
queue.Insert(new QueueEntry<int, string>(1, "min"));
var newMin = new QueueEntry<int, string>(3, "item2");
queue.Insert(newMin);
queue.Insert(new QueueEntry<int, string>(5, "item3"));
queue.DeleteMin();
Assert.AreEqual(newMin, queue.Min);
}
[Test]
public void FindMax_Returns_CorrectMax()
{
var queue = new PriorityQueue<int, string>();
queue.Insert(new QueueEntry<int, string>(1, "min"));
queue.Insert(new QueueEntry<int, string>(3, "item2"));
var maxItem = new QueueEntry<int, string>(7, "item4");
queue.Insert(maxItem);
queue.Insert(new QueueEntry<int, string>(5, "item3"));
Assert.AreEqual(maxItem, queue.Max);
}
[Test]
public void DeleteSingleItem_FindMin_ThrowInvalidOperation()
{
var queue = new PriorityQueue<int, string>();
queue.Insert(new QueueEntry<int, string>(1, "min"));
queue.DeleteMin();
//QueueEntry<int, string> x;
//Assert.Throws(typeof(InvalidOperationException), () => x = queue.Min);
Assert.IsNull(queue.Min);
}
[Test]
public void DeleteSingleItem_FindMax_ThrowInvalidOperation()
{
var queue = new PriorityQueue<int, string>();
queue.Insert(new QueueEntry<int, string>(1, "min"));
queue.DeleteMax();
//QueueEntry<int, string> x;
//Assert.Throws(typeof(InvalidOperationException), () => x = queue.Max);
Assert.IsNull(queue.Max);
}
}