Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.3.2/SimSharp-3.3.2/Collections/SimplePriorityQueue.cs @ 18023

Last change on this file since 18023 was 18023, checked in by jkarder, 3 years ago

#3065: update Sim# to 3.3.2

File size: 28.9 KB
Line 
1#region License
2/*
3The MIT License (MIT)
4
5Copyright (c) 2013 Daniel "BlueRaja" Pflughoeft
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in
15all copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23THE SOFTWARE.
24 */
25#endregion
26
27using System;
28using System.Collections;
29using System.Collections.Generic;
30using System.Linq;
31
32namespace SimSharp {
33  /// <summary>
34  /// A simplified priority queue implementation.  Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than
35  /// FastPriorityQueue
36  /// Methods tagged as O(1) or O(log n) are assuming there are no duplicates.  Duplicates may increase the algorithmic complexity.
37  /// </summary>
38  /// <remarks>
39  /// Original sources from https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
40  /// </remarks>
41  /// <typeparam name="TItem">The type to enqueue</typeparam>
42  /// <typeparam name="TPriority">The priority-type to use for nodes.  Must extend IComparable&lt;TPriority&gt;</typeparam>
43  public class SimplePriorityQueue<TItem, TPriority> : IEnumerable<TItem>
44      where TPriority : IComparable<TPriority> {
45    private class SimpleNode : GenericPriorityQueueNode<TPriority> {
46      public TItem Data { get; private set; }
47
48      public SimpleNode(TItem data) {
49        Data = data;
50      }
51    }
52
53    private const int INITIAL_QUEUE_SIZE = 64;
54    private readonly GenericPriorityQueue<SimpleNode, TPriority> _queue;
55    private readonly Dictionary<TItem, IList<SimpleNode>> _itemToNodesCache;
56    private readonly IList<SimpleNode> _nullNodesCache;
57
58    /// <summary>
59    /// Instantiate a new Priority Queue
60    /// </summary>
61    public SimplePriorityQueue() : this(Comparer<TPriority>.Default) { }
62
63    /// <summary>
64    /// Instantiate a new Priority Queue
65    /// </summary>
66    /// <param name="comparer">The comparer used to compare TPriority values.  Defaults to Comparer&lt;TPriority&gt;.default</param>
67    public SimplePriorityQueue(IComparer<TPriority> comparer) : this(comparer.Compare) { }
68
69    /// <summary>
70    /// Instantiate a new Priority Queue
71    /// </summary>
72    /// <param name="comparer">The comparison function to use to compare TPriority values</param>
73    public SimplePriorityQueue(Comparison<TPriority> comparer) {
74      _queue = new GenericPriorityQueue<SimpleNode, TPriority>(INITIAL_QUEUE_SIZE, comparer);
75      _itemToNodesCache = new Dictionary<TItem, IList<SimpleNode>>();
76      _nullNodesCache = new List<SimpleNode>();
77    }
78
79    /// <summary>
80    /// Given an item of type T, returns the exist SimpleNode in the queue
81    /// </summary>
82    private SimpleNode GetExistingNode(TItem item) {
83      if (item == null) {
84        return _nullNodesCache.Count > 0 ? _nullNodesCache[0] : null;
85      }
86
87      IList<SimpleNode> nodes;
88      if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
89        return null;
90      }
91      return nodes[0];
92    }
93
94    /// <summary>
95    /// Adds an item to the Node-cache to allow for many methods to be O(1) or O(log n)
96    /// </summary>
97    private void AddToNodeCache(SimpleNode node) {
98      if (node.Data == null) {
99        _nullNodesCache.Add(node);
100        return;
101      }
102
103      IList<SimpleNode> nodes;
104      if (!_itemToNodesCache.TryGetValue(node.Data, out nodes)) {
105        nodes = new List<SimpleNode>();
106        _itemToNodesCache[node.Data] = nodes;
107      }
108      nodes.Add(node);
109    }
110
111    /// <summary>
112    /// Removes an item to the Node-cache to allow for many methods to be O(1) or O(log n) (assuming no duplicates)
113    /// </summary>
114    private void RemoveFromNodeCache(SimpleNode node) {
115      if (node.Data == null) {
116        _nullNodesCache.Remove(node);
117        return;
118      }
119
120      IList<SimpleNode> nodes;
121      if (!_itemToNodesCache.TryGetValue(node.Data, out nodes)) {
122        return;
123      }
124      nodes.Remove(node);
125      if (nodes.Count == 0) {
126        _itemToNodesCache.Remove(node.Data);
127      }
128    }
129
130    /// <summary>
131    /// Returns the number of nodes in the queue.
132    /// O(1)
133    /// </summary>
134    public int Count {
135      get {
136        return _queue.Count;
137      }
138    }
139
140
141    /// <summary>
142    /// Returns the head of the queue, without removing it (use Dequeue() for that).
143    /// Throws an exception when the queue is empty.
144    /// O(1)
145    /// </summary>
146    public TItem First {
147      get {
148        if (_queue.Count <= 0) {
149          throw new InvalidOperationException("Cannot call .First on an empty queue");
150        }
151
152        return _queue.First.Data;
153      }
154    }
155
156    /// <summary>
157    /// Returns the top priority of the queue.
158    /// Throws an exception when the queue is empty.
159    /// O(1)
160    /// </summary>
161    public TPriority Peek {
162      get {
163        lock (_queue) {
164          if (_queue.Count <= 0) {
165            throw new InvalidOperationException("Cannot call .Peek on an empty queue");
166          }
167          return _queue.First.Priority;
168        }
169      }
170    }
171
172    /// <summary>
173    /// Removes every node from the queue.
174    /// O(n)
175    /// </summary>
176    public void Clear() {
177      _queue.Clear();
178      _itemToNodesCache.Clear();
179      _nullNodesCache.Clear();
180    }
181
182    /// <summary>
183    /// Returns whether the given item is in the queue.
184    /// O(1)
185    /// </summary>
186    public bool Contains(TItem item) {
187      if (item == null) {
188        return _nullNodesCache.Count > 0;
189      }
190      return _itemToNodesCache.ContainsKey(item);
191    }
192
193    /// <summary>
194    /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it.
195    /// If queue is empty, throws an exception
196    /// O(log n)
197    /// </summary>
198    public TItem Dequeue() {
199      if (_queue.Count <= 0) {
200        throw new InvalidOperationException("Cannot call Dequeue() on an empty queue");
201      }
202
203      SimpleNode node = _queue.Dequeue();
204      RemoveFromNodeCache(node);
205      return node.Data;
206    }
207
208    /// <summary>
209    /// Enqueue the item with the given priority, without calling AddToNodeCache(node)
210    /// </summary>
211    /// <param name="item"></param>
212    /// <param name="priority"></param>
213    /// <returns></returns>
214    private SimpleNode EnqueueNoCache(TItem item, TPriority priority) {
215      SimpleNode node = new SimpleNode(item);
216      if (_queue.Count == _queue.MaxSize) {
217        _queue.Resize(_queue.MaxSize * 2 + 1);
218      }
219      _queue.Enqueue(node, priority);
220      return node;
221    }
222
223    /// <summary>
224    /// Enqueue a node to the priority queue.  Lower values are placed in front. Ties are broken by first-in-first-out.
225    /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'.
226    /// Duplicates and null-values are allowed.
227    /// O(log n)
228    /// </summary>
229    public void Enqueue(TItem item, TPriority priority) {
230      IList<SimpleNode> nodes;
231      if (item == null) {
232        nodes = _nullNodesCache;
233      } else if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
234        nodes = new List<SimpleNode>();
235        _itemToNodesCache[item] = nodes;
236      }
237      SimpleNode node = EnqueueNoCache(item, priority);
238      nodes.Add(node);
239    }
240
241    /// <summary>
242    /// 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.
243    /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'.  Null values are allowed.
244    /// Returns true if the node was successfully enqueued; false if it already exists.
245    /// O(log n)
246    /// </summary>
247    public bool EnqueueWithoutDuplicates(TItem item, TPriority priority) {
248      IList<SimpleNode> nodes;
249      if (item == null) {
250        if (_nullNodesCache.Count > 0) {
251          return false;
252        }
253        nodes = _nullNodesCache;
254      } else if (_itemToNodesCache.ContainsKey(item)) {
255        return false;
256      } else {
257        nodes = new List<SimpleNode>();
258        _itemToNodesCache[item] = nodes;
259      }
260      SimpleNode node = EnqueueNoCache(item, priority);
261      nodes.Add(node);
262      return true;
263    }
264
265    /// <summary>
266    /// Removes an item from the queue.  The item does not need to be the head of the queue. 
267    /// If the item is not in the queue, an exception is thrown.  If unsure, check Contains() first.
268    /// If multiple copies of the item are enqueued, only the first one is removed.
269    /// O(log n)
270    /// </summary>
271    public void Remove(TItem item) {
272      SimpleNode removeMe;
273      IList<SimpleNode> nodes;
274      if (item == null) {
275        if (_nullNodesCache.Count == 0) {
276          throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item);
277        }
278        removeMe = _nullNodesCache[0];
279        nodes = _nullNodesCache;
280      } else {
281        if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
282          throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item);
283        }
284        removeMe = nodes[0];
285        if (nodes.Count == 1) {
286          _itemToNodesCache.Remove(item);
287        }
288      }
289      _queue.Remove(removeMe);
290      nodes.Remove(removeMe);
291    }
292
293    /// <summary>
294    /// Call this method to change the priority of an item.
295    /// Calling this method on a item not in the queue will throw an exception.
296    /// If the item is enqueued multiple times, only the first one will be updated.
297    /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
298    /// to update all of them, please wrap your items in a wrapper class so they can be distinguished).
299    /// O(log n)
300    /// </summary>
301    public void UpdatePriority(TItem item, TPriority priority) {
302      SimpleNode updateMe = GetExistingNode(item);
303      if (updateMe == null) {
304        throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + item);
305      }
306      _queue.UpdatePriority(updateMe, priority);
307    }
308
309    /// <summary>
310    /// Returns the priority of the given item.
311    /// Calling this method on a item not in the queue will throw an exception.
312    /// If the item is enqueued multiple times, only the priority of the first will be returned.
313    /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
314    /// to query all their priorities, please wrap your items in a wrapper class so they can be distinguished).
315    /// O(1)
316    /// </summary>
317    public TPriority GetPriority(TItem item) {
318      SimpleNode findMe = GetExistingNode(item);
319      if (findMe == null) {
320        throw new InvalidOperationException("Cannot call GetPriority() on a node which is not enqueued: " + item);
321      }
322      return findMe.Priority;
323    }
324
325    #region Try* methods for multithreading
326    /// Get the head of the queue, without removing it (use TryDequeue() for that).
327    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and First
328    /// Returns true if successful, false otherwise
329    /// O(1)
330    public bool TryFirst(out TItem first) {
331      if (_queue.Count <= 0) {
332        first = default(TItem);
333        return false;
334      }
335
336      first = _queue.First.Data;
337      return true;
338    }
339
340    /// <summary>
341    /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and sets it to first.
342    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and Dequeue()
343    /// Returns true if successful; false if queue was empty
344    /// O(log n)
345    /// </summary>
346    public bool TryDequeue(out TItem first) {
347      if (_queue.Count <= 0) {
348        first = default(TItem);
349        return false;
350      }
351
352      SimpleNode node = _queue.Dequeue();
353      first = node.Data;
354      RemoveFromNodeCache(node);
355      return true;
356    }
357
358    /// <summary>
359    /// Attempts to remove an item from the queue.  The item does not need to be the head of the queue. 
360    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and Remove()
361    /// Returns true if the item was successfully removed, false if it wasn't in the queue.
362    /// If multiple copies of the item are enqueued, only the first one is removed.
363    /// O(log n)
364    /// </summary>
365    public bool TryRemove(TItem item) {
366      SimpleNode removeMe;
367      IList<SimpleNode> nodes;
368      if (item == null) {
369        if (_nullNodesCache.Count == 0) {
370          return false;
371        }
372        removeMe = _nullNodesCache[0];
373        nodes = _nullNodesCache;
374      } else {
375        if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
376          return false;
377        }
378        removeMe = nodes[0];
379        if (nodes.Count == 1) {
380          _itemToNodesCache.Remove(item);
381        }
382      }
383      _queue.Remove(removeMe);
384      nodes.Remove(removeMe);
385      return true;
386    }
387
388    /// <summary>
389    /// Call this method to change the priority of an item.
390    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and UpdatePriority()
391    /// If the item is enqueued multiple times, only the first one will be updated.
392    /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
393    /// to update all of them, please wrap your items in a wrapper class so they can be distinguished).
394    /// Returns true if the item priority was updated, false otherwise.
395    /// O(log n)
396    /// </summary>
397    public bool TryUpdatePriority(TItem item, TPriority priority) {
398      SimpleNode updateMe = GetExistingNode(item);
399      if (updateMe == null) {
400        return false;
401      }
402      _queue.UpdatePriority(updateMe, priority);
403      return true;
404    }
405
406    /// <summary>
407    /// Attempt to get the priority of the given item.
408    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and GetPriority()
409    /// If the item is enqueued multiple times, only the priority of the first will be returned.
410    /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
411    /// to query all their priorities, please wrap your items in a wrapper class so they can be distinguished).
412    /// Returns true if the item was found in the queue, false otherwise
413    /// O(1)
414    /// </summary>
415    public bool TryGetPriority(TItem item, out TPriority priority) {
416      SimpleNode findMe = GetExistingNode(item);
417      if (findMe == null) {
418        priority = default(TPriority);
419        return false;
420      }
421      priority = findMe.Priority;
422      return true;
423    }
424    #endregion
425
426    public IEnumerator<TItem> GetEnumerator() {
427      return _queue.Select(x => x.Data).GetEnumerator();
428    }
429
430    IEnumerator IEnumerable.GetEnumerator() {
431      return GetEnumerator();
432    }
433
434    public bool IsValidQueue() {
435      // Check all items in cache are in the queue
436      foreach (IList<SimpleNode> nodes in _itemToNodesCache.Values) {
437        foreach (SimpleNode node in nodes) {
438          if (!_queue.Contains(node)) {
439            return false;
440          }
441        }
442      }
443
444      // Check all items in queue are in cache
445      foreach (SimpleNode node in _queue) {
446        if (GetExistingNode(node.Data) == null) {
447          return false;
448        }
449      }
450
451      // Check queue structure itself
452      return _queue.IsValidQueue();
453    }
454  }
455
456  /// <summary>
457  /// A simplified priority queue implementation.  Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than
458  /// FastPriorityQueue
459  /// Methods tagged as O(1) or O(log n) are assuming there are no duplicates.  Duplicates may increase the algorithmic complexity.
460  /// </summary>
461  /// <typeparam name="TItem">The type to enqueue</typeparam>
462  public class SimplePriorityQueue<TItem> : IEnumerable<TItem> where TItem : IComparable<TItem> {
463    private class SimpleNode : GenericPriorityQueueNode, IComparable<SimpleNode>, IComparable {
464      public TItem Data { get; private set; }
465      private Comparison<TItem> _comparer;
466
467      public SimpleNode(TItem data, Comparison<TItem> comparer) {
468        Data = data;
469        _comparer = comparer;
470      }
471
472      public int CompareTo(SimpleNode other) {
473        if (other == null) return 1;
474        return _comparer(Data, other.Data);
475      }
476
477      public int CompareTo(object obj) {
478        if (obj is SimpleNode other) return CompareTo(other);
479        if (obj == null) return 1;
480        throw new ArgumentException("can only compare to objects of type SimpleNode.");
481      }
482    }
483
484    private const int INITIAL_QUEUE_SIZE = 64;
485    private readonly GenericPriorityQueue<SimpleNode> _queue;
486    private readonly Dictionary<TItem, IList<SimpleNode>> _itemToNodesCache;
487    private readonly IList<SimpleNode> _nullNodesCache;
488    private readonly Comparison<TItem> _comparer;
489
490    /// <summary>
491    /// Instantiate a new Priority Queue
492    /// </summary>
493    public SimplePriorityQueue() : this(Comparer<TItem>.Default) { }
494
495    /// <summary>
496    /// Instantiate a new Priority Queue
497    /// </summary>
498    /// <param name="comparer">The comparer used to compare TPriority values.  Defaults to Comparer&lt;TPriority&gt;.default</param>
499    public SimplePriorityQueue(IComparer<TItem> comparer) : this(comparer.Compare) { }
500
501    /// <summary>
502    /// Instantiate a new Priority Queue
503    /// </summary>
504    /// <param name="comparer">The comparison function to use to compare TPriority values</param>
505    public SimplePriorityQueue(Comparison<TItem> comparer) {
506      _comparer = comparer;
507      // the inner priority queue will always use the default comparer which in turn makes use of the specified comparer
508      _queue = new GenericPriorityQueue<SimpleNode>(INITIAL_QUEUE_SIZE, Comparer<SimpleNode>.Default);
509      _itemToNodesCache = new Dictionary<TItem, IList<SimpleNode>>();
510      _nullNodesCache = new List<SimpleNode>();
511    }
512
513    /// <summary>
514    /// Given an item of type T, returns the exist SimpleNode in the queue
515    /// </summary>
516    private SimpleNode GetExistingNode(TItem item) {
517      if (item == null) {
518        return _nullNodesCache.Count > 0 ? _nullNodesCache[0] : null;
519      }
520
521      IList<SimpleNode> nodes;
522      if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
523        return null;
524      }
525      return nodes[0];
526    }
527
528    /// <summary>
529    /// Adds an item to the Node-cache to allow for many methods to be O(1) or O(log n)
530    /// </summary>
531    private void AddToNodeCache(SimpleNode node) {
532      if (node.Data == null) {
533        _nullNodesCache.Add(node);
534        return;
535      }
536
537      IList<SimpleNode> nodes;
538      if (!_itemToNodesCache.TryGetValue(node.Data, out nodes)) {
539        nodes = new List<SimpleNode>();
540        _itemToNodesCache[node.Data] = nodes;
541      }
542      nodes.Add(node);
543    }
544
545    /// <summary>
546    /// Removes an item to the Node-cache to allow for many methods to be O(1) or O(log n) (assuming no duplicates)
547    /// </summary>
548    private void RemoveFromNodeCache(SimpleNode node) {
549      if (node.Data == null) {
550        _nullNodesCache.Remove(node);
551        return;
552      }
553
554      IList<SimpleNode> nodes;
555      if (!_itemToNodesCache.TryGetValue(node.Data, out nodes)) {
556        return;
557      }
558      nodes.Remove(node);
559      if (nodes.Count == 0) {
560        _itemToNodesCache.Remove(node.Data);
561      }
562    }
563
564    /// <summary>
565    /// Returns the number of nodes in the queue.
566    /// O(1)
567    /// </summary>
568    public int Count {
569      get {
570        return _queue.Count;
571      }
572    }
573
574
575    /// <summary>
576    /// Returns the head of the queue, without removing it (use Dequeue() for that).
577    /// Throws an exception when the queue is empty.
578    /// O(1)
579    /// </summary>
580    public TItem First {
581      get {
582        if (_queue.Count <= 0) {
583          throw new InvalidOperationException("Cannot call .First on an empty queue");
584        }
585
586        return _queue.First.Data;
587      }
588    }
589
590    /// <summary>
591    /// Removes every node from the queue.
592    /// O(n)
593    /// </summary>
594    public void Clear() {
595      _queue.Clear();
596      _itemToNodesCache.Clear();
597      _nullNodesCache.Clear();
598    }
599
600    /// <summary>
601    /// Returns whether the given item is in the queue.
602    /// O(1)
603    /// </summary>
604    public bool Contains(TItem item) {
605      if (item == null) {
606        return _nullNodesCache.Count > 0;
607      }
608      return _itemToNodesCache.ContainsKey(item);
609    }
610
611    /// <summary>
612    /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it.
613    /// If queue is empty, throws an exception
614    /// O(log n)
615    /// </summary>
616    public TItem Dequeue() {
617      if (_queue.Count <= 0) {
618        throw new InvalidOperationException("Cannot call Dequeue() on an empty queue");
619      }
620
621      SimpleNode node = _queue.Dequeue();
622      RemoveFromNodeCache(node);
623      return node.Data;
624    }
625
626    /// <summary>
627    /// Enqueue the item with the given priority, without calling AddToNodeCache(node)
628    /// </summary>
629    /// <param name="item"></param>
630    /// <returns></returns>
631    private SimpleNode EnqueueNoCache(TItem item) {
632      SimpleNode node = new SimpleNode(item, _comparer);
633      if (_queue.Count == _queue.MaxSize) {
634        _queue.Resize(_queue.MaxSize * 2 + 1);
635      }
636      _queue.Enqueue(node);
637      return node;
638    }
639
640    /// <summary>
641    /// Enqueue a node to the priority queue.  Lower values are placed in front. Ties are broken by first-in-first-out.
642    /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'.
643    /// Duplicates and null-values are allowed.
644    /// O(log n)
645    /// </summary>
646    public void Enqueue(TItem item) {
647      IList<SimpleNode> nodes;
648      if (item == null) {
649        nodes = _nullNodesCache;
650      } else if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
651        nodes = new List<SimpleNode>();
652        _itemToNodesCache[item] = nodes;
653      }
654      SimpleNode node = EnqueueNoCache(item);
655      nodes.Add(node);
656    }
657
658    /// <summary>
659    /// 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.
660    /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'.  Null values are allowed.
661    /// Returns true if the node was successfully enqueued; false if it already exists.
662    /// O(log n)
663    /// </summary>
664    public bool EnqueueWithoutDuplicates(TItem item) {
665      IList<SimpleNode> nodes;
666      if (item == null) {
667        if (_nullNodesCache.Count > 0) {
668          return false;
669        }
670        nodes = _nullNodesCache;
671      } else if (_itemToNodesCache.ContainsKey(item)) {
672        return false;
673      } else {
674        nodes = new List<SimpleNode>();
675        _itemToNodesCache[item] = nodes;
676      }
677      SimpleNode node = EnqueueNoCache(item);
678      nodes.Add(node);
679      return true;
680    }
681
682    /// <summary>
683    /// Removes an item from the queue.  The item does not need to be the head of the queue. 
684    /// If the item is not in the queue, an exception is thrown.  If unsure, check Contains() first.
685    /// If multiple copies of the item are enqueued, only the first one is removed.
686    /// O(log n)
687    /// </summary>
688    public void Remove(TItem item) {
689      SimpleNode removeMe;
690      IList<SimpleNode> nodes;
691      if (item == null) {
692        if (_nullNodesCache.Count == 0) {
693          throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item);
694        }
695        removeMe = _nullNodesCache[0];
696        nodes = _nullNodesCache;
697      } else {
698        if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
699          throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item);
700        }
701        removeMe = nodes[0];
702        if (nodes.Count == 1) {
703          _itemToNodesCache.Remove(item);
704        }
705      }
706      _queue.Remove(removeMe);
707      nodes.Remove(removeMe);
708    }
709
710    /// <summary>
711    /// Call this method to change the priority of an item.
712    /// Calling this method on a item not in the queue will throw an exception.
713    /// If the item is enqueued multiple times, only the first one will be updated.
714    /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
715    /// to update all of them, please wrap your items in a wrapper class so they can be distinguished).
716    /// O(log n)
717    /// </summary>
718    public void UpdatePriority(TItem item) {
719      SimpleNode updateMe = GetExistingNode(item);
720      if (updateMe == null) {
721        throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + item);
722      }
723      _queue.UpdatePriority(updateMe);
724    }
725
726    #region Try* methods for multithreading
727    /// Get the head of the queue, without removing it (use TryDequeue() for that).
728    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and First
729    /// Returns true if successful, false otherwise
730    /// O(1)
731    public bool TryFirst(out TItem first) {
732      if (_queue.Count <= 0) {
733        first = default(TItem);
734        return false;
735      }
736
737      first = _queue.First.Data;
738      return true;
739    }
740
741    /// <summary>
742    /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and sets it to first.
743    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and Dequeue()
744    /// Returns true if successful; false if queue was empty
745    /// O(log n)
746    /// </summary>
747    public bool TryDequeue(out TItem first) {
748      if (_queue.Count <= 0) {
749        first = default(TItem);
750        return false;
751      }
752
753      SimpleNode node = _queue.Dequeue();
754      first = node.Data;
755      RemoveFromNodeCache(node);
756      return true;
757    }
758
759    /// <summary>
760    /// Attempts to remove an item from the queue.  The item does not need to be the head of the queue. 
761    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and Remove()
762    /// Returns true if the item was successfully removed, false if it wasn't in the queue.
763    /// If multiple copies of the item are enqueued, only the first one is removed.
764    /// O(log n)
765    /// </summary>
766    public bool TryRemove(TItem item) {
767      SimpleNode removeMe;
768      IList<SimpleNode> nodes;
769      if (item == null) {
770        if (_nullNodesCache.Count == 0) {
771          return false;
772        }
773        removeMe = _nullNodesCache[0];
774        nodes = _nullNodesCache;
775      } else {
776        if (!_itemToNodesCache.TryGetValue(item, out nodes)) {
777          return false;
778        }
779        removeMe = nodes[0];
780        if (nodes.Count == 1) {
781          _itemToNodesCache.Remove(item);
782        }
783      }
784      _queue.Remove(removeMe);
785      nodes.Remove(removeMe);
786      return true;
787    }
788
789    /// <summary>
790    /// Call this method to change the priority of an item.
791    /// Useful for multi-threading, where the queue may become empty between calls to Contains() and UpdatePriority()
792    /// If the item is enqueued multiple times, only the first one will be updated.
793    /// (If your requirements are complex enough that you need to enqueue the same item multiple times <i>and</i> be able
794    /// to update all of them, please wrap your items in a wrapper class so they can be distinguished).
795    /// Returns true if the item priority was updated, false otherwise.
796    /// O(log n)
797    /// </summary>
798    public bool TryUpdatePriority(TItem item) {
799      SimpleNode updateMe = GetExistingNode(item);
800      if (updateMe == null) {
801        return false;
802      }
803      _queue.UpdatePriority(updateMe);
804      return true;
805    }
806    #endregion
807
808    public IEnumerator<TItem> GetEnumerator() {
809      return _queue.Select(x => x.Data).GetEnumerator();
810    }
811
812    IEnumerator IEnumerable.GetEnumerator() {
813      return GetEnumerator();
814    }
815
816    public bool IsValidQueue() {
817      // Check all items in cache are in the queue
818      foreach (IList<SimpleNode> nodes in _itemToNodesCache.Values) {
819        foreach (SimpleNode node in nodes) {
820          if (!_queue.Contains(node)) {
821            return false;
822          }
823        }
824      }
825
826      // Check all items in queue are in cache
827      foreach (SimpleNode node in _queue) {
828        if (GetExistingNode(node.Data) == null) {
829          return false;
830        }
831      }
832
833      // Check queue structure itself
834      return _queue.IsValidQueue();
835    }
836  }
837}
Note: See TracBrowser for help on using the repository browser.