Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2645_ContextAlgorithms/HeuristicLab.ExtLibs/HeuristicLab.SimSharp/3.0.9/SimSharp-3.0.9/Collections/EventQueue.cs @ 16654

Last change on this file since 16654 was 12657, checked in by abeham, 9 years ago

#2420: Added Sim# as an ExtLib plugin

File size: 9.5 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 NET_VERSION_4_5
51        [MethodImpl(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 NET_VERSION_4_5
62        [MethodImpl(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 NET_VERSION_4_5
72        [MethodImpl(MethodImplOptions.AggressiveInlining)]
73#endif
74    public EventQueueNode Enqueue(DateTime priority, Event @event) {
75      var node = new EventQueueNode {
76        Priority = priority,
77        Event = @event,
78        QueueIndex = ++_numNodes,
79        InsertionIndex = _numNodesEverEnqueued++
80      };
81      _nodes[_numNodes] = node;
82      CascadeUp(_nodes[_numNodes]);
83      return node;
84    }
85
86#if NET_VERSION_4_5
87        [MethodImpl(MethodImplOptions.AggressiveInlining)]
88#endif
89    private void Swap(EventQueueNode node1, EventQueueNode node2) {
90      //Swap the nodes
91      _nodes[node1.QueueIndex] = node2;
92      _nodes[node2.QueueIndex] = node1;
93
94      //Swap their indicies
95      int temp = node1.QueueIndex;
96      node1.QueueIndex = node2.QueueIndex;
97      node2.QueueIndex = temp;
98    }
99
100    //Performance appears to be slightly better when this is NOT inlined o_O
101    private void CascadeUp(EventQueueNode node) {
102      //aka Heapify-up
103      int parent = node.QueueIndex / 2;
104      while (parent >= 1) {
105        EventQueueNode parentNode = _nodes[parent];
106        if (HasHigherPriority(parentNode, node))
107          break;
108
109        //Node has lower priority value, so move it up the heap
110        Swap(node, parentNode); //For some reason, this is faster with Swap() rather than (less..?) individual operations, like in CascadeDown()
111
112        parent = node.QueueIndex / 2;
113      }
114    }
115
116#if NET_VERSION_4_5
117        [MethodImpl(MethodImplOptions.AggressiveInlining)]
118#endif
119    private void CascadeDown(EventQueueNode node) {
120      //aka Heapify-down
121      EventQueueNode newParent;
122      int finalQueueIndex = node.QueueIndex;
123      while (true) {
124        newParent = node;
125        int childLeftIndex = 2 * finalQueueIndex;
126
127        //Check if the left-child is higher-priority than the current node
128        if (childLeftIndex > _numNodes) {
129          //This could be placed outside the loop, but then we'd have to check newParent != node twice
130          node.QueueIndex = finalQueueIndex;
131          _nodes[finalQueueIndex] = node;
132          break;
133        }
134
135        EventQueueNode childLeft = _nodes[childLeftIndex];
136        if (HasHigherPriority(childLeft, newParent)) {
137          newParent = childLeft;
138        }
139
140        //Check if the right-child is higher-priority than either the current node or the left child
141        int childRightIndex = childLeftIndex + 1;
142        if (childRightIndex <= _numNodes) {
143          EventQueueNode childRight = _nodes[childRightIndex];
144          if (HasHigherPriority(childRight, newParent)) {
145            newParent = childRight;
146          }
147        }
148
149        //If either of the children has higher (smaller) priority, swap and continue cascading
150        if (newParent != node) {
151          //Move new parent to its new index.  node will be moved once, at the end
152          //Doing it this way is one less assignment operation than calling Swap()
153          _nodes[finalQueueIndex] = newParent;
154
155          int temp = newParent.QueueIndex;
156          newParent.QueueIndex = finalQueueIndex;
157          finalQueueIndex = temp;
158        } else {
159          //See note above
160          node.QueueIndex = finalQueueIndex;
161          _nodes[finalQueueIndex] = node;
162          break;
163        }
164      }
165    }
166
167    /// <summary>
168    /// Returns true if 'higher' has higher priority than 'lower', false otherwise.
169    /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false
170    /// </summary>
171#if NET_VERSION_4_5
172        [MethodImpl(MethodImplOptions.AggressiveInlining)]
173#endif
174    private bool HasHigherPriority(EventQueueNode higher, EventQueueNode lower) {
175      return (higher.Priority < lower.Priority ||
176          (higher.Priority == lower.Priority && higher.InsertionIndex < lower.InsertionIndex));
177    }
178
179    /// <summary>
180    /// Removes the head of the queue (node with highest priority; ties are broken by order of insertion), and returns it.  O(log n)
181    /// </summary>
182    public EventQueueNode Dequeue() {
183      EventQueueNode returnMe = _nodes[1];
184      Remove(returnMe);
185      return returnMe;
186    }
187
188    /// <summary>
189    /// Returns the head of the queue, without removing it (use Dequeue() for that).  O(1)
190    /// </summary>
191    public EventQueueNode First {
192      get {
193        return _nodes[1];
194      }
195    }
196
197    /// <summary>
198    /// This method must be called on a node every time its priority changes while it is in the queue. 
199    /// <b>Forgetting to call this method will result in a corrupted queue!</b>
200    /// O(log n)
201    /// </summary>
202#if NET_VERSION_4_5
203        [MethodImpl(MethodImplOptions.AggressiveInlining)]
204#endif
205    public void UpdatePriority(EventQueueNode node, DateTime priority) {
206      node.Priority = priority;
207      OnNodeUpdated(node);
208    }
209
210    internal void OnNodeUpdated(EventQueueNode node) {
211      //Bubble the updated node up or down as appropriate
212      int parentIndex = node.QueueIndex / 2;
213      EventQueueNode parentNode = _nodes[parentIndex];
214
215      if (parentIndex > 0 && HasHigherPriority(node, parentNode)) {
216        CascadeUp(node);
217      } else {
218        //Note that CascadeDown will be called if parentNode == node (that is, node is the root)
219        CascadeDown(node);
220      }
221    }
222
223    /// <summary>
224    /// Removes a node from the queue.  Note that the node does not need to be the head of the queue.  O(log n)
225    /// </summary>
226    public void Remove(EventQueueNode node) {
227      if (!Contains(node)) {
228        return;
229      }
230      if (_numNodes <= 1) {
231        _nodes[1] = null;
232        _numNodes = 0;
233        return;
234      }
235
236      //Make sure the node is the last node in the queue
237      bool wasSwapped = false;
238      EventQueueNode formerLastNode = _nodes[_numNodes];
239      if (node.QueueIndex != _numNodes) {
240        //Swap the node with the last node
241        Swap(node, formerLastNode);
242        wasSwapped = true;
243      }
244
245      _numNodes--;
246      _nodes[node.QueueIndex] = null;
247
248      if (wasSwapped) {
249        //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate
250        OnNodeUpdated(formerLastNode);
251      }
252    }
253
254    public IEnumerator<EventQueueNode> GetEnumerator() {
255      for (int i = 1; i <= _numNodes; i++)
256        yield return _nodes[i];
257    }
258
259    IEnumerator IEnumerable.GetEnumerator() {
260      return GetEnumerator();
261    }
262
263    /// <summary>
264    /// <b>Should not be called in production code.</b>
265    /// Checks to make sure the queue is still in a valid state.  Used for testing/debugging the queue.
266    /// </summary>
267    public bool IsValidQueue() {
268      for (int i = 1; i < _nodes.Length; i++) {
269        if (_nodes[i] != null) {
270          int childLeftIndex = 2 * i;
271          if (childLeftIndex < _nodes.Length && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i]))
272            return false;
273
274          int childRightIndex = childLeftIndex + 1;
275          if (childRightIndex < _nodes.Length && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i]))
276            return false;
277        }
278      }
279      return true;
280    }
281  }
282}
Note: See TracBrowser for help on using the repository browser.