1 /** 2 * A priority queue implementation for use with the anasi graph library. 3 * License: Boost Software License 1.0. 4 */ 5 module anansi.container.priorityqueue; 6 7 import std.algorithm, std.exception, std.functional, std.math, std.range, std.traits; 8 9 /** 10 * A priority queue implementation. Items with the highest priority (as defined 11 * by the supplied predicate) come off the queue first. You can change the 12 * order of the queue by supplying a custom predicate, or overriding opCmp 13 * on the type being queued. 14 */ 15 struct PriorityQueue(T, alias Predicate = "a > b") { 16 this(Stuff)(Stuff stuff) if (isInputRange!Stuff && 17 isImplicitlyConvertible!(ElementType!Stuff, T)) { 18 foreach(s; stuff) 19 push(s); 20 } 21 22 public void push(T value) { 23 _payload ~= move(value); 24 bubbleUp(_payload.length-1); 25 } 26 27 @property size_t length() const { 28 return _payload.length; 29 } 30 31 @property bool empty() const { 32 return _payload.length == 0; 33 } 34 35 /** 36 * Fetches a reference to the highest-priority element in the queue. 37 */ 38 public ref inout(T) front() inout 39 in { 40 assert (_payload.length > 0, "The queue may not be empty."); 41 } 42 body { 43 return _payload[0]; 44 } 45 46 /** 47 * Removes the highest-priority item from the queue. The queue must not 48 * be empty. 49 */ 50 public void pop() 51 in { 52 assert (_payload.length > 0, "The queue may not be empty."); 53 } 54 body { 55 _payload[0] = move(_payload[$-1]); 56 _payload = _payload[0 .. $-1]; 57 if (_payload.length > 1) 58 siftDown(0); 59 } 60 61 public void updateIf(alias FindPredicate, 62 alias UpdateOperation)() { 63 alias isItem = unaryFun!FindPredicate; 64 alias update = unaryFun!UpdateOperation; 65 66 foreach(i, ref t; _payload) { 67 if (isItem(t)) { 68 update(t); 69 reHeap(i); 70 } 71 } 72 } 73 74 public void updateIf(FindPredicate, UpdateOperation)( 75 FindPredicate isItem, UpdateOperation update) { 76 77 foreach(i, ref t; _payload) { 78 if (isItem(t)) { 79 update(t); 80 reHeap(i); 81 break; 82 } 83 } 84 } 85 86 /** 87 * Repairs the heap, assuming that the value at the given index 88 * has changed. 89 */ 90 private void reHeap(size_t index) { 91 if (!bubbleUp(index)) 92 siftDown(index); 93 } 94 95 /** 96 * Recursively enforces the heap property of the items in the 97 * payload array, from the bottom up. 98 */ 99 private bool bubbleUp(size_t index) { 100 bool result = false; 101 if( index > 0) { 102 size_t parentIndex = ((index-1) / 2); 103 if (greaterThan(_payload[index], _payload[parentIndex])) { 104 swap(_payload[parentIndex], _payload[index]); 105 bubbleUp(parentIndex); 106 result = true; 107 } 108 } 109 return result; 110 } 111 112 /** 113 * Recursively enforces the heap property of the items in the payload 114 * array, from the top down. 115 */ 116 private void siftDown(size_t index) { 117 immutable size_t leftChild = (index * 2) + 1; 118 immutable size_t rightChild = leftChild + 1; 119 120 immutable size_t nChildren = 121 sgn(cast(int)_payload.length - cast(int)rightChild) + 1; 122 123 switch(nChildren) { 124 case 0: 125 // no children, we're already at the leaf 126 break; 127 128 case 1: 129 // one child, by definition the left one. 130 if (greaterThan(_payload[leftChild], _payload[index])) { 131 swap(_payload[index], _payload[leftChild]); 132 } 133 break; 134 135 case 2: 136 // two children, examine the larger child and push the values 137 // down the tree 138 immutable size_t target = 139 greaterThan(_payload[leftChild], _payload[rightChild]) ? 140 leftChild : rightChild; 141 if (greaterThan(_payload[target], _payload[index])) { 142 swap(_payload[index], _payload[target]); 143 siftDown(target); 144 } 145 break; 146 147 default: 148 enforce(false, "This should never happen."); 149 break; 150 } 151 } 152 153 alias greaterThan = binaryFun!Predicate; 154 155 /** 156 * The items in the queue, stored as implicit tree that satisfies the heap 157 * property. 158 */ 159 private T[] _payload; 160 } 161 162 // ---------------------------------------------------------------------------- 163 // 164 // ---------------------------------------------------------------------------- 165 166 version (unittest) { 167 import std.conv, std.stdio; 168 } 169 170 unittest { 171 writeln("PriorityQueue: Default construction should yield an empty queue."); 172 PriorityQueue!int q; 173 assert (q.empty, "Default-constructed queue must be empty."); 174 assert (q.length == 0, "Default-constructed queue must have 0 elements."); 175 } 176 177 unittest { 178 writeln("PriorityQueue: Construction from a range yields a valid queue."); 179 auto q = PriorityQueue!int([1, 3, 5, 7, 9, 2, 4, 6, 8, 10]); 180 assert (q.length == 10, 181 "Expected a queue of length 10, got " ~ to!string(q.length)); 182 auto expected = 10; 183 while (!q.empty) { 184 assert (q.front == expected, 185 "Expected a value of " ~ to!string(expected) ~ 186 ", got " ~ to!string(q.front)); 187 --expected; 188 q.pop(); 189 } 190 } 191 192 unittest { 193 writeln("PriorityQueue: Copying creates an independant queue."); 194 auto q = PriorityQueue!int([1, 3, 5, 7, 9, 2, 4, 6, 8, 10]); 195 auto p = q; 196 q.push(11); 197 assert(q.length == 11 && p.length == 10); 198 199 p.pop(); 200 assert(q.length == 11 && p.length == 9); 201 } 202 203 unittest { 204 writeln("PriorityQueue: Pushing element results in a valid queue."); 205 PriorityQueue!int q; 206 207 foreach(n; [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]) { 208 q.push(n); 209 } 210 211 auto expected = 10; 212 while (!q.empty) { 213 assert (q.front == expected, 214 "Expected a value of " ~ to!string(expected) ~ 215 ", got " ~ to!string(q.front)); 216 --expected; 217 q.pop(); 218 } 219 } 220 221 unittest { 222 writeln("PriorityQueue: Custom predicates can change the order of the queue."); 223 auto q = PriorityQueue!(int, "a < b")([1, 3, 5, 7, 9, 2, 4, 6, 8, 10]); 224 assert (q.length == 10, 225 "Expected a queue of length 10, got " ~ to!string(q.length)); 226 227 foreach (expected; 1 .. 11) { 228 assert (q.front == expected, 229 "Expected a value of " ~ to!string(expected) ~ 230 ", got " ~ to!string(q.front)); 231 q.pop(); 232 } 233 } 234 235 unittest { 236 writeln("PriorityQueue: Increasing a key value with a static function reorders the queue."); 237 auto q = PriorityQueue!(int)([1, 3, 5, 7, 9, 2, 4, 6, 8, 10]); 238 q.updateIf!("a == 7", (ref int x) => x = 42)(); 239 240 foreach (expected; [42, 10, 9, 8, 6, 5, 4, 3, 2, 1]) { 241 assert (q.front == expected, 242 "Expected a value of " ~ to!string(expected) ~ 243 ", got " ~ to!string(q.front)); 244 q.pop(); 245 } 246 } 247 248 unittest { 249 writeln("PriorityQueue: Decreasing a key value with a static function reorders the queue."); 250 auto q = PriorityQueue!(int)([1, 3, 5, 7, 9, 2, 4, 6, 8, 10]); 251 q.updateIf!("a == 7", (ref int x) => x = 0)(); 252 253 foreach (expected; [10, 9, 8, 6, 5, 4, 3, 2, 1, 0]) { 254 assert (q.front == expected, 255 "Expected a value of " ~ to!string(expected) ~ 256 ", got " ~ to!string(q.front)); 257 q.pop(); 258 } 259 } 260 261 unittest { 262 writeln("PriorityQueue: Increasing a key value with a static function reorders the queue."); 263 auto q = PriorityQueue!(int)([1, 3, 5, 7, 9, 2, 4, 6, 8, 10]); 264 const int y = 7; 265 const int z = 42; 266 q.updateIf((int x) => x == y, (ref int x) => x = z); 267 268 foreach (expected; [42, 10, 9, 8, 6, 5, 4, 3, 2, 1]) { 269 assert (q.front == expected, 270 "Expected a value of " ~ to!string(expected) ~ 271 ", got " ~ to!string(q.front)); 272 q.pop(); 273 } 274 } 275 276 unittest { 277 writeln("PriorityQueue: Decreasing a key value with a static function reorders the queue."); 278 auto q = PriorityQueue!(int)([1, 3, 5, 7, 9, 2, 4, 6, 8, 10]); 279 const int y = 7; 280 const int z = 0; 281 q.updateIf((int x) => x == y, (ref int x) => x = z); 282 283 foreach (expected; [10, 9, 8, 6, 5, 4, 3, 2, 1, 0]) { 284 assert (q.front == expected, 285 "Expected a value of " ~ to!string(expected) ~ 286 ", got " ~ to!string(q.front)); 287 q.pop(); 288 } 289 } 290 291 unittest { 292 writeln("PriorityQueue: Not changing a key value leaves the queue alone."); 293 auto q = PriorityQueue!(int)([1, 3, 5, 7, 9, 2, 4, 6, 8, 10]); 294 q.updateIf!("a == 7", (ref int x) => x = x)(); 295 296 foreach (expected; [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]) { 297 assert (q.front == expected, 298 "Expected a value of " ~ to!string(expected) ~ 299 ", got " ~ to!string(q.front)); 300 q.pop(); 301 } 302 }