From bb6a850e2540e10707b23bd5829030f389441960 Mon Sep 17 00:00:00 2001 From: BlueRaja Date: Fri, 12 May 2017 02:28:21 -0500 Subject: [PATCH] Added Try* methods for better multithreading support --- .../SimplePriorityQueueTests.cs | 223 ++++++++++++++++++ Priority Queue/SimplePriorityQueue.cs | 189 ++++++++++++--- 2 files changed, 382 insertions(+), 30 deletions(-) diff --git a/Priority Queue Tests/SimplePriorityQueueTests.cs b/Priority Queue Tests/SimplePriorityQueueTests.cs index 90909ae..7560065 100644 --- a/Priority Queue Tests/SimplePriorityQueueTests.cs +++ b/Priority Queue Tests/SimplePriorityQueueTests.cs @@ -18,6 +18,12 @@ protected override bool IsValidQueue() return Queue.IsValidQueue(); } + protected void EnqueueWithoutDuplicates(Node node) + { + Queue.EnqueueWithoutDuplicates(node, node.Priority); + Assert.IsTrue(IsValidQueue()); + } + [Test] public void TestOrderedQueue() { @@ -220,5 +226,222 @@ public void TestGetPriority() Assert.AreEqual(2, Queue.GetPriority(node2)); Assert.AreEqual(3, Queue.GetPriority(node3)); } + + [Test] + public void TestEnqueueWithoutDuplicatesNormal() + { + Node node1 = new Node(1); + Node node2 = new Node(2); + Node node3 = new Node(3); + + EnqueueWithoutDuplicates(node1); + EnqueueWithoutDuplicates(node2); + EnqueueWithoutDuplicates(node3); + + Assert.AreEqual(node1, Dequeue()); + Assert.AreEqual(node2, Dequeue()); + Assert.AreEqual(node3, Dequeue()); + } + + [Test] + public void TestEnqueueWithoutDuplicatesWithDuplicates() + { + Node node = new Node(1); + + EnqueueWithoutDuplicates(node); + EnqueueWithoutDuplicates(node); + EnqueueWithoutDuplicates(node); + + Assert.AreEqual(1, Queue.Count); + Assert.AreEqual(node, Dequeue()); + } + + [Test] + public void TestEnqueueWithoutDuplicatesWithDuplicatesMoreComplicated() + { + Node node11 = new Node(1); + Node node12 = new Node(1); + Node node2 = new Node(2); + + EnqueueWithoutDuplicates(node11); + EnqueueWithoutDuplicates(node12); + EnqueueWithoutDuplicates(node12); + EnqueueWithoutDuplicates(node2); + EnqueueWithoutDuplicates(node11); + + Assert.AreEqual(3, Queue.Count); + Assert.AreEqual(node11, Dequeue()); + Assert.AreEqual(node12, Dequeue()); + Assert.AreEqual(node2, Dequeue()); + } + + [Test] + public void TestTryFirstEmptyQueue() + { + Node first; + Assert.IsFalse(Queue.TryFirst(out first)); + Assert.IsNull(first); + } + + [Test] + public void TestTryFirstWithItems() + { + Node node = new Node(1); + Node first; + + Enqueue(node); + + Assert.IsTrue(Queue.TryFirst(out first)); + Assert.AreEqual(node, first); + Assert.AreEqual(1, Queue.Count); + } + + [Test] + public void TestTryDequeueEmptyQueue() + { + Node first; + Assert.IsFalse(Queue.TryDequeue(out first)); + Assert.IsNull(first); + } + + [Test] + public void TestTryDequeueWithItems() + { + Node node = new Node(1); + Node first; + + Enqueue(node); + + Assert.IsTrue(Queue.TryDequeue(out first)); + Assert.AreEqual(node, first); + Assert.AreEqual(0, Queue.Count); + } + + [Test] + public void TestTryRemoveEmptyQueue() + { + Node node = new Node(1); + + Assert.IsFalse(Queue.TryRemove(node)); + } + + [Test] + public void TestTryRemoveItemInQueue() + { + Node node1 = new Node(1); + Node node2 = new Node(2); + Node node3 = new Node(3); + + Enqueue(node1); + Enqueue(node2); + Enqueue(node3); + + Assert.IsTrue(Queue.Contains(node2)); + Assert.IsTrue(Queue.TryRemove(node2)); + Assert.IsFalse(Queue.Contains(node2)); + Assert.IsFalse(Queue.TryRemove(node2)); + + Assert.IsTrue(Queue.Contains(node3)); + Assert.IsTrue(Queue.TryRemove(node3)); + Assert.IsFalse(Queue.Contains(node3)); + Assert.IsFalse(Queue.TryRemove(node3)); + + Assert.IsTrue(Queue.Contains(node1)); + Assert.IsTrue(Queue.TryRemove(node1)); + Assert.IsFalse(Queue.Contains(node1)); + Assert.IsFalse(Queue.TryRemove(node1)); + } + + [Test] + public void TestTryRemoveItemNotInQueue() + { + Node node1 = new Node(1); + Node node2 = new Node(2); + Node node3 = new Node(3); + + Enqueue(node1); + Enqueue(node2); + + Assert.IsFalse(Queue.TryRemove(node3)); + } + + [Test] + public void TestTryUpdatePriorityEmptyQueue() + { + Node node = new Node(1); + + Assert.IsFalse(Queue.TryUpdatePriority(node, 2)); + } + + [Test] + public void TestTryUpdatePriorityItemInQueue() + { + Node node1 = new Node(1); + Node node2 = new Node(2); + Node node3 = new Node(3); + + Enqueue(node1); + Enqueue(node2); + Enqueue(node3); + + Assert.IsTrue(Queue.TryUpdatePriority(node2, 0)); + + Assert.AreEqual(3, Queue.Count); + Assert.AreEqual(node2, Dequeue()); + } + + [Test] + public void TestTryUpdatePriorityItemNotInQueue() + { + Node node1 = new Node(1); + Node node2 = new Node(2); + Node node3 = new Node(3); + + Enqueue(node1); + Enqueue(node2); + + Assert.IsFalse(Queue.TryUpdatePriority(node3, 0)); + } + + [Test] + public void TestTryGetPriorityEmptyQueue() + { + Node node = new Node(1); + float priority; + + Assert.IsFalse(Queue.TryGetPriority(node, out priority)); + Assert.AreEqual(0, priority); + } + + [Test] + public void TestTryGetPriorityItemInQueue() + { + Node node1 = new Node(1); + Node node2 = new Node(2); + Node node3 = new Node(3); + float priority; + + Enqueue(node1); + Enqueue(node2); + Enqueue(node3); + + Assert.IsTrue(Queue.TryGetPriority(node2, out priority)); + Assert.AreEqual(2, priority); + } + + [Test] + public void TestTryGetPriorityItemNotInQueue() + { + Node node1 = new Node(1); + Node node2 = new Node(2); + Node node3 = new Node(3); + float priority; + + Enqueue(node1); + Enqueue(node2); + + Assert.IsFalse(Queue.TryGetPriority(node3, out priority)); + Assert.AreEqual(0, priority); + } } } \ No newline at end of file diff --git a/Priority Queue/SimplePriorityQueue.cs b/Priority Queue/SimplePriorityQueue.cs index b33336e..f3b88f6 100644 --- a/Priority Queue/SimplePriorityQueue.cs +++ b/Priority Queue/SimplePriorityQueue.cs @@ -59,7 +59,7 @@ private SimpleNode GetExistingNode(TItem item) return node; } } - throw new InvalidOperationException("Item cannot be found in queue: " + item); + return null; } /// @@ -94,8 +94,7 @@ public TItem First throw new InvalidOperationException("Cannot call .First on an empty queue"); } - SimpleNode first = _queue.First; - return (first != null ? first.Data : default(TItem)); + return _queue.First.Data; } } } @@ -120,15 +119,7 @@ public bool Contains(TItem item) { lock(_queue) { - var comparer = EqualityComparer.Default; - foreach (var node in _queue) - { - if (comparer.Equals(node.Data, item)) - { - return true; - } - } - return false; + return GetExistingNode(item) != null; } } @@ -151,6 +142,19 @@ public TItem Dequeue() } } + /// + /// Enqueue the item with the given priority, without calling lock(_queue) + /// + private void EnqueueNoLock(TItem item, TPriority priority) + { + SimpleNode node = new SimpleNode(item); + if(_queue.Count == _queue.MaxSize) + { + _queue.Resize(_queue.MaxSize * 2 + 1); + } + _queue.Enqueue(node, priority); + } + /// /// 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'. @@ -161,12 +165,26 @@ public void Enqueue(TItem item, TPriority priority) { lock(_queue) { - SimpleNode node = new SimpleNode(item); - if(_queue.Count == _queue.MaxSize) + EnqueueNoLock(item, priority); + } + } + + /// + /// Enqueue a node to the priority queue if it doesn't already exist. 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'. + /// Returns true if the node was successfully enqueued; false if it already exists + /// O(n) + /// + public bool EnqueueWithoutDuplicates(TItem item, TPriority priority) + { + lock(_queue) + { + if(this.Contains(item)) { - _queue.Resize(_queue.MaxSize*2 + 1); + return false; } - _queue.Enqueue(node, priority); + EnqueueNoLock(item, priority); + return true; } } @@ -180,14 +198,12 @@ public void Remove(TItem item) { lock(_queue) { - try - { - _queue.Remove(GetExistingNode(item)); - } - catch(InvalidOperationException ex) + SimpleNode removeMe = GetExistingNode(item); + if (removeMe == null) { - throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item, ex); + throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item); } + _queue.Remove(removeMe); } } @@ -203,15 +219,12 @@ public void UpdatePriority(TItem item, TPriority priority) { lock (_queue) { - try + SimpleNode updateMe = GetExistingNode(item); + if (updateMe == null) { - 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); + throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + item); } + _queue.UpdatePriority(updateMe, priority); } } @@ -227,9 +240,125 @@ public TPriority GetPriority(TItem item) { lock (_queue) { - return GetExistingNode(item).Priority; + SimpleNode findMe = GetExistingNode(item); + if(findMe == null) + { + throw new InvalidOperationException("Cannot call GetPriority() on a node which is not enqueued: " + item); + } + return findMe.Priority; + } + } + + #region Try* methods for multithreading + /// Get the head of the queue, without removing it (use TryDequeue() for that). + /// Useful for multi-threading, where the queue may become empty between calls to Contains() and First + /// Returns true if successful, false otherwise + /// O(1) + public bool TryFirst(out TItem first) + { + lock(_queue) + { + if(_queue.Count <= 0) + { + first = default(TItem); + return false; + } + + first = _queue.First.Data; + return true; + } + } + + /// + /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and sets it to first. + /// Useful for multi-threading, where the queue may become empty between calls to Contains() and Dequeue() + /// Returns true if successful; false if queue was empty + /// O(log n) + /// + public bool TryDequeue(out TItem first) + { + lock(_queue) + { + if(_queue.Count <= 0) + { + first = default(TItem); + return false; + } + + SimpleNode node = _queue.Dequeue(); + first = node.Data; + return true; + } + } + + /// + /// Attempts to remove an item from the queue. The item does not need to be the head of the queue. + /// Useful for multi-threading, where the queue may become empty between calls to Contains() and Remove() + /// Returns true if the item was successfully removed, false if it wasn't in the queue. + /// If multiple copies of the item are enqueued, only the first one is removed. + /// O(n) + /// + public bool TryRemove(TItem item) + { + lock(_queue) + { + SimpleNode removeMe = GetExistingNode(item); + if(removeMe == null) + { + return false; + } + _queue.Remove(removeMe); + return true; + } + } + + /// + /// Call this method to change the priority of an item. + /// Useful for multi-threading, where the queue may become empty between calls to Contains() and UpdatePriority() + /// 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 and be able + /// to update all of them, please wrap your items in a wrapper class so they can be distinguished). + /// Returns true if the item priority was updated, false otherwise. + /// O(n) + /// + public bool TryUpdatePriority(TItem item, TPriority priority) + { + lock(_queue) + { + SimpleNode updateMe = GetExistingNode(item); + if(updateMe == null) + { + return false; + } + _queue.UpdatePriority(updateMe, priority); + return true; + } + } + + /// + /// Attempt to get the priority of the given item. + /// Useful for multi-threading, where the queue may become empty between calls to Contains() and GetPriority() + /// If the item is enqueued multiple times, only the priority of the first will be returned. + /// (If your requirements are complex enough that you need to enqueue the same item multiple times and be able + /// to query all their priorities, please wrap your items in a wrapper class so they can be distinguished). + /// Returns true if the item was found in the queue, false otherwise + /// O(n) (O(1) if item == queue.First) + /// + public bool TryGetPriority(TItem item, out TPriority priority) + { + lock(_queue) + { + SimpleNode findMe = GetExistingNode(item); + if(findMe == null) + { + priority = default(TPriority); + return false; + } + priority = findMe.Priority; + return true; } } + #endregion public IEnumerator GetEnumerator() {