| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library priority_queue; |
| 6 |
| 7 import 'dart:collection'; |
| 8 import 'dart:math'; |
| 9 |
| 10 /** |
| 11 * A priority used for the priority queue. Subclasses only need to implement |
| 12 * the compareTo function. |
| 13 */ |
| 14 abstract class Priority implements Comparable { |
| 15 /** |
| 16 * Return < 0 if other is bigger, >0 if other is smaller, 0 if they are equal. |
| 17 */ |
| 18 int compareTo(Priority other); |
| 19 bool operator<(Priority other) => compareTo(other) < 0; |
| 20 bool operator>(Priority other) => compareTo(other) > 0; |
| 21 bool operator==(Priority other) => compareTo(other) == 0; |
| 22 } |
| 23 |
| 24 /** |
| 25 * Priority based on integers. |
| 26 */ |
| 27 class IntPriority extends Priority { |
| 28 int priority; |
| 29 IntPriority(int this.priority); |
| 30 |
| 31 int compareTo(IntPriority other) { |
| 32 return priority - other.priority; |
| 33 } |
| 34 String toString() => "$priority"; |
| 35 } |
| 36 |
| 37 /** |
| 38 * An element of a priority queue. The type is used restriction based |
| 39 * querying of the queues. |
| 40 */ |
| 41 abstract class TypedElement<V> { |
| 42 bool typeEquals(var other); |
| 43 } |
| 44 |
| 45 class StringTypedElement<V> extends TypedElement{ |
| 46 String type; |
| 47 V value; |
| 48 StringTypedElement(String this.type, V this.value); |
| 49 bool typeEquals(String otherType) => otherType == type; |
| 50 String toString() => "<Type: $type, Value: $value>"; |
| 51 } |
| 52 |
| 53 |
| 54 /** |
| 55 * A priority node in a priority queue. A priority node contains all of the |
| 56 * values for a given priority in a given queue. It is part of a linked |
| 57 * list of nodes, with prev and next pointers. |
| 58 */ |
| 59 class PriorityNode<N extends TypedElement, T extends Priority> { |
| 60 T priority; |
| 61 Queue<N> values; |
| 62 PriorityNode prev; |
| 63 PriorityNode next; |
| 64 PriorityNode(N initialNode, T this.priority) |
| 65 : values = new Queue<N>() { |
| 66 add(initialNode); |
| 67 } |
| 68 |
| 69 void add(N n) => values.add(n); |
| 70 |
| 71 bool remove(N n) => values.remove(n); |
| 72 |
| 73 N removeFirst() => values.removeFirst(); |
| 74 |
| 75 bool get isEmpty => values.isEmpty; |
| 76 |
| 77 N get first => values.first; |
| 78 |
| 79 String toString() => "Priority: $priority $values"; |
| 80 } |
| 81 |
| 82 /** |
| 83 * A priority queue with a FIFO property for nodes with same priority. |
| 84 * The queue guarantees that nodes are returned in the same order they |
| 85 * are added for a given priority. |
| 86 * For type safety this queue is guarded by the elements being subclasses of |
| 87 * TypedElement - this is not strictly neccesary since we never actually |
| 88 * use the value or type of the nodes. |
| 89 */ |
| 90 class PriorityQueue<N extends TypedElement, P extends Priority> { |
| 91 PriorityNode<N, P> head; |
| 92 int length = 0; |
| 93 |
| 94 void add(N value, P priority) { |
| 95 length++; |
| 96 if (head == null) { |
| 97 head = new PriorityNode<N, P>(value, priority); |
| 98 return; |
| 99 } |
| 100 assert(head.next == null); |
| 101 var node = head; |
| 102 while (node.prev != null && node.priority > priority) { |
| 103 node = node.prev; |
| 104 } |
| 105 if (node.priority == priority) { |
| 106 node.add(value); |
| 107 } else if (node.priority < priority) { |
| 108 var newNode = new PriorityNode<N, P>(value, priority); |
| 109 newNode.next = node.next; |
| 110 if (node.next != null) node.next.prev = newNode; |
| 111 newNode.prev = node; |
| 112 node.next = newNode; |
| 113 if (node == head) head = newNode; |
| 114 } else { |
| 115 var newNode = new PriorityNode<N, P>(value, priority); |
| 116 node.prev = newNode; |
| 117 newNode.next = node; |
| 118 } |
| 119 } |
| 120 |
| 121 N get first => head.first; |
| 122 |
| 123 Priority get firstPriority => head.priority; |
| 124 |
| 125 bool get isEmpty => head == null; |
| 126 |
| 127 N removeFirst() { |
| 128 if (isEmpty) throw "Can't get element from empty queue"; |
| 129 var value = head.removeFirst(); |
| 130 if (head.isEmpty) { |
| 131 if (head.prev != null) { |
| 132 head.prev.next = null; |
| 133 } |
| 134 head = head.prev; |
| 135 } |
| 136 length--; |
| 137 assert(head == null || head.next == null); |
| 138 return value; |
| 139 } |
| 140 |
| 141 String toString() { |
| 142 if (head == null) return "Empty priority queue"; |
| 143 var node = head; |
| 144 var buffer = new StringBuffer(); |
| 145 while (node.prev != null) { |
| 146 buffer.writeln(node); |
| 147 node = node.prev; |
| 148 } |
| 149 buffer.writeln(node); |
| 150 return buffer.toString(); |
| 151 } |
| 152 } |
| 153 |
| 154 /** |
| 155 * Implements a specialized priority queue that efficiently allows getting |
| 156 * the highest priorized node that adheres to a set of restrictions. |
| 157 * Most notably it allows to get the highest priority node where the node's |
| 158 * type is not in an exclude list. |
| 159 * In addition, the queue has a number of properties: |
| 160 * The queue has fifo semantics for nodes with the same priority and type, |
| 161 * i.e., if nodes a and b are added to the queue with priority x and type z |
| 162 * then a is returned first iff a was added before b |
| 163 * For different types with the same priority no guarantees are given, but |
| 164 * the returned values try to be fair by returning from the biggest list of |
| 165 * tasks in case of priority clash. (This could be fixed by adding timestamps |
| 166 * to every node, that is _only_ used when collisions occur, not for |
| 167 * insertions) |
| 168 */ |
| 169 class RestrictViewPriorityQueue<N extends TypedElement, P extends Priority> { |
| 170 // We can't use the basic dart priority queue since it does not guarantee |
| 171 // FIFO for items with the same order. This is currently not uptimized for |
| 172 // different N, if many different N is expected here we should have a |
| 173 // priority queue instead of a list. |
| 174 List<PriorityQueue<N, P>> restrictedQueues = new List<PriorityQueue<N, P>>(); |
| 175 PriorityQueue<N, P> mainQueue = new PriorityQueue<N, P>(); |
| 176 |
| 177 void add(N value, P priority) { |
| 178 for (var queue in restrictedQueues) { |
| 179 if (queue.first.value == value) { |
| 180 queue.add(value, priority); |
| 181 } |
| 182 } |
| 183 mainQueue.add(value, priority); |
| 184 } |
| 185 |
| 186 bool get isEmpty => restrictedQueues.length + mainQueue.length == 0; |
| 187 |
| 188 int get length => restrictedQueues.fold(0, (v, e) => v + element.length) + |
| 189 mainQueue.length; |
| 190 |
| 191 PriorityQueue getRestricted(List<N> restrictions) { |
| 192 var current = null; |
| 193 // Find highest restricted priority. |
| 194 for (var queue in restrictedQueues) { |
| 195 if (!restrictions.any((e) => queue.head.first.typeEquals(e))) { |
| 196 if (current == null || queue.firstPriority > current.firstPriority) { |
| 197 current = queue; |
| 198 } else if (current.firstPriority == queue.firstPriority) { |
| 199 current = queue.length > current.length ? queue : current; |
| 200 } |
| 201 } |
| 202 } |
| 203 return current; |
| 204 } |
| 205 |
| 206 N get first { |
| 207 if (isEmpty) throw "Trying to remove node from empty queue"; |
| 208 var candidate = getRestricted([]); |
| 209 if (candidate != null && |
| 210 (mainQueue.isEmpty || |
| 211 mainQueue.first.priority < candidate.first.priority)) { |
| 212 return candidate.first; |
| 213 } |
| 214 return mainQueue.isEmpty ? null : mainQueue.first; |
| 215 } |
| 216 |
| 217 /** |
| 218 * Returns the node that under the given set of restrictions. |
| 219 * If the queue is empty this function throws. |
| 220 * If the queue is not empty, but no node exists that adheres to the |
| 221 * restrictions we return null. |
| 222 */ |
| 223 N removeFirst({List restrictions: const []}) { |
| 224 if (isEmpty) throw "Trying to remove node from empty queue"; |
| 225 var candidate = getRestricted(restrictions); |
| 226 |
| 227 if (candidate != null && |
| 228 (mainQueue.isEmpty || |
| 229 mainQueue.firstPriority < candidate.firstPriority)) { |
| 230 var value = candidate.removeFirst(); |
| 231 if (candidate.isEmpty) restrictedQueues.remove(candidate); |
| 232 return value; |
| 233 } |
| 234 while (!mainQueue.isEmpty) { |
| 235 var currentPriority = mainQueue.firstPriority; |
| 236 var current = mainQueue.removeFirst(); |
| 237 if (!restrictions.any((e) => current.typeEquals(e))) { |
| 238 return current; |
| 239 } else { |
| 240 var restrictedQueue = restrictedQueues |
| 241 .firstWhere((e) => current.typeEquals(e.first.type), |
| 242 orElse: () => null); |
| 243 if (restrictedQueue == null) { |
| 244 restrictedQueue = new PriorityQueue<N, P>(); |
| 245 restrictedQueues.add(restrictedQueue); |
| 246 } |
| 247 restrictedQueue.add(current, currentPriority); |
| 248 } |
| 249 } |
| 250 } |
| 251 |
| 252 String toString() { |
| 253 if (isEmpty) return "Empty queue"; |
| 254 var buffer = new StringBuffer(); |
| 255 if (!restrictedQueues.isEmpty) { |
| 256 buffer.writeln("Restricted queues"); |
| 257 for (var queue in restrictedQueues) { |
| 258 buffer.writeln("$queue"); |
| 259 } |
| 260 } |
| 261 buffer.writeln("Main queue:"); |
| 262 buffer.writeln("$mainQueue"); |
| 263 return buffer.toString(); |
| 264 } |
| 265 } |
| 266 |
| 267 /// TEMPORARY TESTING AND PERFORMANCE |
| 268 void main([args]) { |
| 269 stress(new RestrictViewPriorityQueue<StringTypedElement, IntPriority>()); |
| 270 } |
| 271 |
| 272 void stress(queue) { |
| 273 final int SIZE = 50000; |
| 274 Random random = new Random(29); |
| 275 |
| 276 var priorities = [1, 2, 3, 16, 32, 42, 56, 57, 59, 90]; |
| 277 var values = [new StringTypedElement('safari', 'foo'), |
| 278 new StringTypedElement('ie', 'bar'), |
| 279 new StringTypedElement('ff', 'foobar'), |
| 280 new StringTypedElement('dartium', 'barfoo'), |
| 281 new StringTypedElement('chrome', 'hest'), |
| 282 new StringTypedElement('drt', 'fisk')]; |
| 283 |
| 284 var restricted = ['safari', 'chrome']; |
| 285 |
| 286 |
| 287 void addRandom() { |
| 288 queue.add(values[random.nextInt(values.length)], |
| 289 new IntPriority(priorities[random.nextInt(priorities.length)])); |
| 290 } |
| 291 |
| 292 var stopwatch = new Stopwatch()..start(); |
| 293 while(queue.length < SIZE) { |
| 294 addRandom(); |
| 295 } |
| 296 |
| 297 stopwatch.stop(); |
| 298 print("Adding took: ${stopwatch.elapsedMilliseconds}"); |
| 299 print("Queue length: ${queue.length}"); |
| 300 |
| 301 stopwatch = new Stopwatch()..start(); |
| 302 while(queue.length > 0) { |
| 303 queue.removeFirst(); |
| 304 } |
| 305 stopwatch.stop(); |
| 306 print("Remowing took: ${stopwatch.elapsedMilliseconds}"); |
| 307 print("Queue length: ${queue.length}"); |
| 308 |
| 309 |
| 310 print("Restricted add/remove"); |
| 311 while(queue.length < SIZE) { |
| 312 addRandom(); |
| 313 } |
| 314 |
| 315 for (int i = 0; i < SIZE; i++) { |
| 316 if (random.nextDouble() < 0.5) { |
| 317 queue.removeFirst(restrictions: restricted); |
| 318 } else { |
| 319 queue.removeFirst(); |
| 320 } |
| 321 addRandom(); |
| 322 } |
| 323 } |
| OLD | NEW |