1 /**
2   *
3   * Copyright:  Copyright (C) 2008 Chris Wright.  All rights reserved.
4   * License:    BSD style: $(LICENSE)
5   * Version:    Oct 2008: Initial release
6   * Author:     Chris Wright, aka dhasenan
7   *
8   */
9 
10 module tango.util.container.more.Heap;
11 
12 private import tango.core.Exception;
13 
14 bool minHeapCompare(T)(T a, T b) {return a <= b;}
15 bool maxHeapCompare(T)(T a, T b) {return a >= b;}
16 void defaultHeapSwap(T)(T t, size_t index) {}
17 
18 /** A heap is a data structure where you can insert items in random order and extract them in sorted order. 
19   * Pushing an element into the heap takes O(lg n) and popping the top of the heap takes O(lg n). Heaps are 
20   * thus popular for sorting, among other things.
21   * 
22   * No opApply is provided, since most people would expect this to return the contents in sorted order,
23   * not do significant heap allocation, not modify the collection, and complete in linear time. This
24   * combination is not possible with a heap. 
25   *
26   * Note: always pass by reference when modifying a heap. 
27   *
28   * The template arguments to the heap are:
29   *     T       = the element type
30   *     Compare = a function called when ordering elements. Its signature should be bool(T, T).
31   *               see minHeapCompare() and maxHeapCompare() for examples.
32   *     Move    = a function called when swapping elements. Its signature should be void(T, size_t).
33   *               The default does nothing, and should suffice for most users. You 
34   *               probably want to keep this function small; it's called O(log N) 
35   *               times per insertion or removal.
36 */
37 
38 struct Heap (T, alias Compare = minHeapCompare!(T), alias Move = defaultHeapSwap!(T))
39 {
40         // The actual data.
41         private T[]     heap;
42         
43         // The index of the cell into which the next element will go.
44         private size_t    next;
45 
46         /** Inserts the given element into the heap. */
47         void push (T t)
48         {
49                 auto index = next++;
50                 while (heap.length <= index)
51                        heap.length = 2 * heap.length + 32;
52 
53                 heap [index] = t;
54                 Move (t, index);
55                 fixup (index);
56         }
57 
58         /** Inserts all elements in the given array into the heap. */
59         void push (T[] array)
60         {
61                 if (heap.length < next + array.length)
62                         heap.length = next + array.length + 32;
63 
64                 foreach (t; array) push (t);
65         }
66 
67         /** Removes the top of this heap and returns it. */
68         T pop ()
69         {
70                 return removeAt (0);
71         }
72 
73         /** Remove the every instance that matches the given item. */
74         void removeAll (T t)
75         {
76                 // TODO: this is slower than it could be.
77                 // I am reasonably certain we can do the O(n) scan, but I want to
78                 // look at it a bit more.
79                 while (remove (t)) {}
80         }
81 
82         /** Remove the first instance that matches the given item. 
83           * Returns: true iff the item was found, otherwise false. */
84         bool remove (T t)
85         {
86                 foreach (i, a; heap)
87                 {
88                         if (a is t || a == t)
89                         {
90                                 removeAt (i);
91                                 return true;
92                         }
93                 }
94                 return false;
95         }
96 
97         /** Remove the element at the given index from the heap.
98           * The index is according to the heap's internal layout; you are 
99           * responsible for making sure the index is correct.
100           * The heap invariant is maintained. */
101         T removeAt (size_t index)
102         {
103                 if (next <= index)
104                 {
105                         throw new NoSuchElementException ("Heap :: tried to remove an"
106                                 ~ " element with index greater than the size of the heap "
107                                 ~ "(did you call pop() from an empty heap?)");
108                 }
109                 next--;
110                 auto t = heap[index];
111                 // if next == index, then we have nothing valid on the heap
112                 // so popping does nothing but change the length
113                 // the other calls are irrelevant, but we surely don't want to
114                 // call Move with invalid data
115                 if (next > index)
116                 {
117                         heap[index] = heap[next];
118                         Move(heap[index], index);
119                         fixdown(index);
120 
121                         // added via ticket 1885 (kudos to wolfwood)
122                         if (heap[index] is heap[next])
123                             fixup(index);
124                 }
125                 return t;
126         }
127 
128         /** Gets the value at the top of the heap without removing it. */
129         T peek ()
130         {
131                 assert (next > 0);
132                 return heap[0];
133         }
134 
135         /** Returns the number of elements in this heap. */
136         @property const size_t size ()
137         {
138                 return next;
139         }
140 
141         /** Reset this heap. */
142         void clear ()
143         {
144                 next = 0;
145         }
146 
147         /** reset this heap, and use the provided host for value elements */
148         void clear (T[] host)
149         {
150                 this.heap = host;
151                 clear();
152         }
153 
154         /** Get the reserved capacity of this heap. */
155         const size_t capacity ()
156         {
157                 return heap.length;
158         }
159 
160         /** Reserve enough space in this heap for value elements. The reserved space is truncated or extended as necessary. If the value is less than the number of elements already in the heap, throw an exception. */
161         size_t capacity (size_t value)
162         {
163                 if (value < next)
164                 {
165                         throw new IllegalArgumentException ("Heap :: illegal truncation");
166                 }
167                 heap.length = value;
168                 return value;
169         }
170 
171         /** Return a shallow copy of this heap. */
172         Heap clone ()
173         {
174                 Heap other;
175                 other.heap = this.heap.dup;
176                 other.next = this.next;
177                 return other;
178         }
179 
180         // Get the index of the parent for the element at the given index.
181         private const size_t parent (size_t index)
182         {
183                 return (index - 1) / 2;
184         }
185 
186         // Having just inserted, restore the heap invariant (that a node's value is greater than its children)
187         private void fixup (size_t index)
188         {
189                 if (index == 0) return;
190                 size_t par = parent (index);
191                 if (!Compare(heap[par], heap[index]))
192                 {
193                         swap (par, index);
194                         fixup (par);
195                 }
196         }
197 
198         // Having just removed and replaced the top of the heap with the last inserted element,
199         // restore the heap invariant.
200         private void fixdown (size_t index)
201         {
202                 size_t left = 2 * index + 1;
203                 size_t down;
204                 if (left >= next)
205                 {
206                         return;
207                 }
208 
209                 if (left == next - 1)
210                 {
211                         down = left;
212                 }
213                 else if (Compare (heap[left], heap[left + 1]))
214                 {
215                         down = left;
216                 }
217                 else
218                 {
219                         down = left + 1;
220                 }
221 
222                 if (!Compare(heap[index], heap[down]))
223                 {
224                         swap (index, down);
225                         fixdown (down);
226                 }
227         }
228 
229         // Swap two elements in the array.
230         private void swap (size_t a, size_t b)
231         {
232                 auto t1 = heap[a];
233                 auto t2 = heap[b];
234                 heap[a] = t2;
235                 Move(t2, a);
236                 heap[b] = t1;
237                 Move(t1, b);
238         }
239         
240         alias pop       remove;
241         
242         void opOpAssign(immutable(char)[] s : "~")(T t)
243         {
244             push(t);
245         }
246         
247         void opOpAssign(immutable(char)[] s : "~")(T[] array)
248         {
249             push(array);
250         }
251 }
252 
253 
254 /** A minheap implementation. This will have the smallest item as the top of the heap. 
255   *
256   * Note: always pass by reference when modifying a heap. 
257   *
258 */
259 
260 template MinHeap(T)
261 {
262         alias Heap!(T, minHeapCompare) MinHeap;
263 }
264 
265 /** A maxheap implementation. This will have the largest item as the top of the heap. 
266   *
267   * Note: always pass by reference when modifying a heap. 
268   *
269 */
270 
271 template MaxHeap(T)
272 {
273         alias Heap!(T, maxHeapCompare) MaxHeap;
274 }
275 
276 
277 
278 debug (UnitTest)
279 {
280 unittest
281 {
282         MinHeap!(uint) h;
283         assert (h.size is 0);
284         h ~= 1;
285         h ~= 3;
286         h ~= 2;
287         h ~= 4;
288         assert (h.size is 4);
289 
290         assert (h.peek() is 1);
291         assert (h.peek() is 1);
292         assert (h.size is 4);
293         h.pop();
294         assert (h.peek() is 2);
295         assert (h.size is 3);
296 }
297 
298 unittest
299 {
300         MinHeap!(uint) h;
301         assert (h.size is 0);
302         h ~= 1;
303         h ~= 3;
304         h ~= 2;
305         h ~= 4;
306         assert (h.size is 4);
307 
308         assert (h.pop() is 1);
309         assert (h.size is 3);
310         assert (h.pop() is 2);
311         assert (h.size is 2);
312         assert (h.pop() is 3);
313         assert (h.size is 1);
314         assert (h.pop() is 4);
315         assert (h.size is 0);
316 }
317 
318 unittest
319 {
320         MaxHeap!(uint) h;
321         h ~= 1;
322         h ~= 3;
323         h ~= 2;
324         h ~= 4;
325 
326         assert (h.pop() is 4);
327         assert (h.pop() is 3);
328         assert (h.pop() is 2);
329         assert (h.pop() is 1);
330 }
331 
332 unittest
333 {
334         MaxHeap!(uint) h;
335         h ~= 1;
336         h ~= 3;
337         h ~= 2;
338         h ~= 4;
339         h.remove(3);
340         assert (h.pop() is 4);
341         assert (h.pop() is 2);
342         assert (h.pop() is 1);
343         assert (h.size == 0);
344 }
345 
346 long[] swapped;
347 size_t[] indices;
348 void onMove(long a, size_t b)
349 {
350         swapped ~= a;
351         indices ~= b;
352 }
353 unittest
354 {
355         // this tests that onMove is called with fixdown
356         swapped = null;
357         indices = null;
358         Heap!(long, minHeapCompare, onMove) h;
359         // no swap
360         h ~= 1;
361         // no swap
362         h ~= 3;
363 
364         // onMove() is called for all insertions
365         swapped = null;
366         indices = null;
367         // pop: you replace the top with the last and
368         // percolate down. So you have to swap once when
369         // popping at a minimum, and that's if you have only two
370         // items in the heap.
371         assert (h.pop() is 1);
372         assert (swapped.length == 1, "" ~ cast(char)('a' + swapped.length));
373         assert (swapped[0] == 3);
374         assert (indices[0] == 0);
375         assert (h.pop() is 3);
376         assert (swapped.length == 1, "" ~ cast(char)('a' + swapped.length));
377 }
378 unittest
379 {
380         // this tests that onMove is called with fixup
381         swapped = null;
382         indices = null;
383         Heap!(long, minHeapCompare, onMove) h;
384         // no swap
385         h ~= 1;
386         // no swap
387         h ~= 3;
388         // swap: move 0 to position 0, 1 to position 2
389         h ~= 0;
390         int n=3; // onMove() called for insertions too
391         if (swapped[n] == 0)
392         {
393                 assert (swapped[n+1] == 1);
394                 assert (indices[n+0] == 0);
395                 assert (indices[n+1] == 2);
396         }
397         else
398         {
399                 assert (swapped[n+1] == 0);
400                 assert (swapped[n+0] == 1);
401                 assert (indices[n+0] == 2);
402                 assert (indices[n+1] == 0);
403         }
404 }
405 
406 unittest
407 {
408         MaxHeap!(uint) h;
409         h ~= 1;
410         h ~= 3;
411         h ~= 2;
412         h ~= 4;
413         auto other = h.clone();
414 
415         assert (other.pop() is 4);
416         assert (other.pop() is 3);
417         assert (other.pop() is 2);
418         assert (other.pop() is 1);
419         assert (h.size is 4, "cloned heap shares data with original heap");
420         assert (h.pop() is 4, "cloned heap shares data with original heap");
421         assert (h.pop() is 3, "cloned heap shares data with original heap");
422         assert (h.pop() is 2, "cloned heap shares data with original heap");
423         assert (h.pop() is 1, "cloned heap shares data with original heap");
424 }
425 }