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 }