Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.0.11/SimSharp-3.0.11/Collections/EventQueue.cs @ 16014

Last change on this file since 16014 was 15972, checked in by abeham, 6 years ago

#2926: Replaced Sim# 3.0.9 with 3.0.11

File size: 10.3 KB
Line 
1using System;
2using System.Collections;
3using System.Collections.Generic;
4
5namespace SimSharp {
6  /// <summary>
7  /// An implementation of a min-Priority Queue using a heap.  Has O(1) .Contains()!
8  /// See https://bitbucket.org/BlueRaja/high-speed-priority-queue-for-c/wiki/Getting%20Started for more information
9  /// </summary>
10  /// <remarks>
11  /// There are modifications so that the type is not generic anymore and can only hold values of type EventQueueNode
12  /// </remarks>
13  public sealed class EventQueue : IEnumerable<EventQueueNode> {
14    private int _numNodes;
15    private readonly EventQueueNode[] _nodes;
16    private long _numNodesEverEnqueued;
17
18    /// <summary>
19    /// Instantiate a new Priority Queue
20    /// </summary>
21    /// <param name="maxNodes">EventQueueNodehe max nodes ever allowed to be enqueued (going over this will cause an exception)</param>
22    public EventQueue(int maxNodes) {
23      _numNodes = 0;
24      _nodes = new EventQueueNode[maxNodes + 1];
25      _numNodesEverEnqueued = 0;
26    }
27
28    /// <summary>
29    /// Returns the number of nodes in the queue.  O(1)
30    /// </summary>
31    public int Count {
32      get {
33        return _numNodes;
34      }
35    }
36
37    /// <summary>
38    /// Returns the maximum number of items that can be enqueued at once in this queue.  Once you hit this number (ie. once Count == MaxSize),
39    /// attempting to enqueue another item will throw an exception.  O(1)
40    /// </summary>
41    public int MaxSize {
42      get {
43        return _nodes.Length - 1;
44      }
45    }
46
47    /// <summary>
48    /// Removes every node from the queue.  O(n) (So, don't do this often!)
49    /// </summary>
50#if AGGRESSIVE_INLINING
51        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
52#endif
53    public void Clear() {
54      Array.Clear(_nodes, 1, _numNodes);
55      _numNodes = 0;
56    }
57
58    /// <summary>
59    /// Returns (in O(1)!) whether the given node is in the queue.  O(1)
60    /// </summary>
61#if AGGRESSIVE_INLINING
62        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
63#endif
64    public bool Contains(EventQueueNode node) {
65      return (_nodes[node.QueueIndex] == node);
66    }
67
68    /// <summary>
69    /// Enqueue a node - .Priority must be set beforehand!  O(log n)
70    /// </summary>
71#if AGGRESSIVE_INLINING
72        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
73#endif
74    public EventQueueNode Enqueue(DateTime primaryPriority, Event @event, int secondaryPriority = 0) {
75      var node = new EventQueueNode {
76        PrimaryPriority = primaryPriority,
77        SecondaryPriority = secondaryPriority,
78        Event = @event,
79        QueueIndex = ++_numNodes,
80        InsertionIndex = _numNodesEverEnqueued++
81      };
82      _nodes[_numNodes] = node;
83      CascadeUp(_nodes[_numNodes]);
84      return node;
85    }
86
87#if AGGRESSIVE_INLINING
88        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
89#endif
90    private void Swap(EventQueueNode node1, EventQueueNode node2) {
91      //Swap the nodes
92      _nodes[node1.QueueIndex] = node2;
93      _nodes[node2.QueueIndex] = node1;
94
95      //Swap their indicies
96      int temp = node1.QueueIndex;
97      node1.QueueIndex = node2.QueueIndex;
98      node2.QueueIndex = temp;
99    }
100
101    //Performance appears to be slightly better when this is NOT inlined o_O
102    private void CascadeUp(EventQueueNode node) {
103      //aka Heapify-up
104      int parent = node.QueueIndex / 2;
105      while (parent >= 1) {
106        EventQueueNode parentNode = _nodes[parent];
107        if (HasHigherPriority(parentNode, node))
108          break;
109
110        //Node has lower priority value, so move it up the heap
111        Swap(node, parentNode); //For some reason, this is faster with Swap() rather than (less..?) individual operations, like in CascadeDown()
112
113        parent = node.QueueIndex / 2;
114      }
115    }
116
117#if AGGRESSIVE_INLINING
118        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
119#endif
120    private void CascadeDown(EventQueueNode node) {
121      //aka Heapify-down
122      EventQueueNode newParent;
123      int finalQueueIndex = node.QueueIndex;
124      while (true) {
125        newParent = node;
126        int childLeftIndex = 2 * finalQueueIndex;
127
128        //Check if the left-child is higher-priority than the current node
129        if (childLeftIndex > _numNodes) {
130          //This could be placed outside the loop, but then we'd have to check newParent != node twice
131          node.QueueIndex = finalQueueIndex;
132          _nodes[finalQueueIndex] = node;
133          break;
134        }
135
136        EventQueueNode childLeft = _nodes[childLeftIndex];
137        if (HasHigherPriority(childLeft, newParent)) {
138          newParent = childLeft;
139        }
140
141        //Check if the right-child is higher-priority than either the current node or the left child
142        int childRightIndex = childLeftIndex + 1;
143        if (childRightIndex <= _numNodes) {
144          EventQueueNode childRight = _nodes[childRightIndex];
145          if (HasHigherPriority(childRight, newParent)) {
146            newParent = childRight;
147          }
148        }
149
150        //If either of the children has higher (smaller) priority, swap and continue cascading
151        if (newParent != node) {
152          //Move new parent to its new index.  node will be moved once, at the end
153          //Doing it this way is one less assignment operation than calling Swap()
154          _nodes[finalQueueIndex] = newParent;
155
156          int temp = newParent.QueueIndex;
157          newParent.QueueIndex = finalQueueIndex;
158          finalQueueIndex = temp;
159        } else {
160          //See note above
161          node.QueueIndex = finalQueueIndex;
162          _nodes[finalQueueIndex] = node;
163          break;
164        }
165      }
166    }
167
168    /// <summary>
169    /// Returns true if 'higher' has higher priority than 'lower', false otherwise.
170    /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false
171    /// </summary>
172#if AGGRESSIVE_INLINING
173        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
174#endif
175    private bool HasHigherPriority(EventQueueNode higher, EventQueueNode lower) {
176      return (higher.PrimaryPriority < lower.PrimaryPriority ||
177          (higher.PrimaryPriority == lower.PrimaryPriority
178            && (higher.SecondaryPriority < lower.SecondaryPriority ||
179            (higher.SecondaryPriority == lower.SecondaryPriority
180              && higher.InsertionIndex < lower.InsertionIndex))));
181    }
182
183    /// <summary>
184    /// Removes the head of the queue (node with highest priority; ties are broken by order of insertion), and returns it.  O(log n)
185    /// </summary>
186    public EventQueueNode Dequeue() {
187      EventQueueNode returnMe = _nodes[1];
188      Remove(returnMe);
189      return returnMe;
190    }
191
192    /// <summary>
193    /// Returns the head of the queue, without removing it (use Dequeue() for that).  O(1)
194    /// </summary>
195    public EventQueueNode First {
196      get {
197        return _nodes[1];
198      }
199    }
200
201    /// <summary>
202    /// This method must be called on a node every time its priority changes while it is in the queue. 
203    /// <b>Forgetting to call this method will result in a corrupted queue!</b>
204    /// O(log n)
205    /// </summary>
206#if AGGRESSIVE_INLINING
207        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
208#endif
209    public void UpdatePriority(EventQueueNode node, DateTime primaryPriority, int secondaryPriority) {
210      node.PrimaryPriority = primaryPriority;
211      node.SecondaryPriority = secondaryPriority;
212      OnNodeUpdated(node);
213    }
214
215    internal void OnNodeUpdated(EventQueueNode node) {
216      //Bubble the updated node up or down as appropriate
217      int parentIndex = node.QueueIndex / 2;
218      EventQueueNode parentNode = _nodes[parentIndex];
219
220      if (parentIndex > 0 && HasHigherPriority(node, parentNode)) {
221        CascadeUp(node);
222      } else {
223        //Note that CascadeDown will be called if parentNode == node (that is, node is the root)
224        CascadeDown(node);
225      }
226    }
227
228    /// <summary>
229    /// Removes a node from the queue.  Note that the node does not need to be the head of the queue.  O(log n)
230    /// </summary>
231    public void Remove(EventQueueNode node) {
232      if (!Contains(node)) {
233        return;
234      }
235      if (_numNodes <= 1) {
236        _nodes[1] = null;
237        _numNodes = 0;
238        return;
239      }
240
241      //Make sure the node is the last node in the queue
242      bool wasSwapped = false;
243      EventQueueNode formerLastNode = _nodes[_numNodes];
244      if (node.QueueIndex != _numNodes) {
245        //Swap the node with the last node
246        Swap(node, formerLastNode);
247        wasSwapped = true;
248      }
249
250      _numNodes--;
251      _nodes[node.QueueIndex] = null;
252
253      if (wasSwapped) {
254        //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate
255        OnNodeUpdated(formerLastNode);
256      }
257    }
258
259    public IEnumerator<EventQueueNode> GetEnumerator() {
260      for (int i = 1; i <= _numNodes; i++)
261        yield return _nodes[i];
262    }
263
264    IEnumerator IEnumerable.GetEnumerator() {
265      return GetEnumerator();
266    }
267
268    /// <summary>
269    /// <b>Should not be called in production code.</b>
270    /// Checks to make sure the queue is still in a valid state.  Used for testing/debugging the queue.
271    /// </summary>
272    public bool IsValidQueue() {
273      for (int i = 1; i < _nodes.Length; i++) {
274        if (_nodes[i] != null) {
275          int childLeftIndex = 2 * i;
276          if (childLeftIndex < _nodes.Length && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i]))
277            return false;
278
279          int childRightIndex = childLeftIndex + 1;
280          if (childRightIndex < _nodes.Length && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i]))
281            return false;
282        }
283      }
284      return true;
285    }
286  }
287}
Note: See TracBrowser for help on using the repository browser.