1 /**
2 This module provides a $(D BinaryHeap) (aka priority queue)
3 adaptor that makes a binary heap out of any user-provided random-access range.
4 
5 This module is a submodule of $(MREF std, container).
6 
7 Source: $(PHOBOSSRC std/container/_binaryheap.d)
8 
9 Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders.
10 
11 License: Distributed under the Boost Software License, Version 1.0.
12 (See accompanying file LICENSE_1_0.txt or copy at $(HTTP
13 boost.org/LICENSE_1_0.txt)).
14 
15 Authors: $(HTTP erdani.com, Andrei Alexandrescu)
16 */
17 module std.container.binaryheap;
18 
19 import std.range.primitives;
20 import std.traits;
21 
22 public import std.container.util;
23 
24 ///
25 @system unittest
26 {
27     import std.algorithm.comparison : equal;
28     import std.range : take;
29     auto maxHeap = heapify([4, 7, 3, 1, 5]);
30     assert(maxHeap.take(3).equal([7, 5, 4]));
31 
32     auto minHeap = heapify!"a > b"([4, 7, 3, 1, 5]);
33     assert(minHeap.take(3).equal([1, 3, 4]));
34 }
35 
36 // BinaryHeap
37 /**
38 Implements a $(HTTP en.wikipedia.org/wiki/Binary_heap, binary heap)
39 container on top of a given random-access range type (usually $(D
40 T[])) or a random-access container type (usually $(D Array!T)). The
41 documentation of $(D BinaryHeap) will refer to the underlying range or
42 container as the $(I store) of the heap.
43 
44 The binary heap induces structure over the underlying store such that
45 accessing the largest element (by using the $(D front) property) is a
46 $(BIGOH 1) operation and extracting it (by using the $(D
47 removeFront()) method) is done fast in $(BIGOH log n) time.
48 
49 If $(D less) is the less-than operator, which is the default option,
50 then $(D BinaryHeap) defines a so-called max-heap that optimizes
51 extraction of the $(I largest) elements. To define a min-heap,
52 instantiate BinaryHeap with $(D "a > b") as its predicate.
53 
54 Simply extracting elements from a $(D BinaryHeap) container is
55 tantamount to lazily fetching elements of $(D Store) in descending
56 order. Extracting elements from the $(D BinaryHeap) to completion
57 leaves the underlying store sorted in ascending order but, again,
58 yields elements in descending order.
59 
60 If $(D Store) is a range, the $(D BinaryHeap) cannot grow beyond the
61 size of that range. If $(D Store) is a container that supports $(D
62 insertBack), the $(D BinaryHeap) may grow by adding elements to the
63 container.
64      */
65 struct BinaryHeap(Store, alias less = "a < b")
66 if (isRandomAccessRange!(Store) || isRandomAccessRange!(typeof(Store.init[])))
67 {
68     import std.algorithm.comparison : min;
69     import std.algorithm.mutation : move, swapAt;
70     import std.algorithm.sorting : HeapOps;
71     import std.exception : enforce;
72     import std.functional : binaryFun;
73     import std.typecons : RefCounted, RefCountedAutoInitialize;
74 
75     static if (isRandomAccessRange!Store)
76         alias Range = Store;
77     else
78         alias Range = typeof(Store.init[]);
79     alias percolate = HeapOps!(less, Range).percolate;
80     alias buildHeap = HeapOps!(less, Range).buildHeap;
81 
82 // Really weird @@BUG@@: if you comment out the "private:" label below,
83 // std.algorithm can't unittest anymore
84 //private:
85 
86     // The payload includes the support store and the effective length
87     private static struct Data
88     {
89         Store _store;
90         size_t _length;
91     }
92     private RefCounted!(Data, RefCountedAutoInitialize.no) _payload;
93     // Comparison predicate
94     private alias comp = binaryFun!(less);
95     // Convenience accessors
_storeBinaryHeap96     private @property ref Store _store()
97     {
98         assert(_payload.refCountedStore.isInitialized);
99         return _payload._store;
100     }
_lengthBinaryHeap101     private @property ref size_t _length()
102     {
103         assert(_payload.refCountedStore.isInitialized);
104         return _payload._length;
105     }
106 
107     // Asserts that the heap property is respected.
assertValidBinaryHeap108     private void assertValid()
109     {
110         debug
111         {
112             import std.conv : to;
113             if (!_payload.refCountedStore.isInitialized) return;
114             if (_length < 2) return;
115             for (size_t n = _length - 1; n >= 1; --n)
116             {
117                 auto parentIdx = (n - 1) / 2;
118                 assert(!comp(_store[parentIdx], _store[n]), to!string(n));
119             }
120         }
121     }
122 
123     // @@@BUG@@@: add private here, std.algorithm doesn't unittest anymore
popBinaryHeap124     /*private*/ void pop(Store store)
125     {
126         assert(!store.empty, "Cannot pop an empty store.");
127         if (store.length == 1) return;
128         auto t1 = store[].moveFront();
129         auto t2 = store[].moveBack();
130         store.front = move(t2);
131         store.back = move(t1);
132         percolate(store[], 0, store.length - 1);
133     }
134 
135 public:
136 
137     /**
138        Converts the store $(D s) into a heap. If $(D initialSize) is
139        specified, only the first $(D initialSize) elements in $(D s)
140        are transformed into a heap, after which the heap can grow up
141        to $(D r.length) (if $(D Store) is a range) or indefinitely (if
142        $(D Store) is a container with $(D insertBack)). Performs
143        $(BIGOH min(r.length, initialSize)) evaluations of $(D less).
144      */
145     this(Store s, size_t initialSize = size_t.max)
146     {
147         acquire(s, initialSize);
148     }
149 
150 /**
151 Takes ownership of a store. After this, manipulating $(D s) may make
152 the heap work incorrectly.
153      */
154     void acquire(Store s, size_t initialSize = size_t.max)
155     {
156         _payload.refCountedStore.ensureInitialized();
157         _store = move(s);
158         _length = min(_store.length, initialSize);
159         if (_length < 2) return;
160         buildHeap(_store[]);
161         assertValid();
162     }
163 
164 /**
165 Takes ownership of a store assuming it already was organized as a
166 heap.
167      */
168     void assume(Store s, size_t initialSize = size_t.max)
169     {
170         _payload.refCountedStore.ensureInitialized();
171         _store = s;
172         _length = min(_store.length, initialSize);
173         assertValid();
174     }
175 
176 /**
177 Clears the heap. Returns the portion of the store from $(D 0) up to
178 $(D length), which satisfies the $(LINK2 https://en.wikipedia.org/wiki/Heap_(data_structure),
179 heap property).
180      */
releaseBinaryHeap181     auto release()
182     {
183         if (!_payload.refCountedStore.isInitialized)
184         {
185             return typeof(_store[0 .. _length]).init;
186         }
187         assertValid();
188         auto result = _store[0 .. _length];
189         _payload = _payload.init;
190         return result;
191     }
192 
193 /**
194 Returns $(D true) if the heap is _empty, $(D false) otherwise.
195      */
emptyBinaryHeap196     @property bool empty()
197     {
198         return !length;
199     }
200 
201 /**
202 Returns a duplicate of the heap. The $(D dup) method is available only if the
203 underlying store supports it.
204      */
205     static if (is(typeof((Store s) { return s.dup; }(Store.init)) == Store))
206     {
dupBinaryHeap207         @property BinaryHeap dup()
208         {
209             BinaryHeap result;
210             if (!_payload.refCountedStore.isInitialized) return result;
211             result.assume(_store.dup, length);
212             return result;
213         }
214     }
215 
216 /**
217 Returns the _length of the heap.
218      */
lengthBinaryHeap219     @property size_t length()
220     {
221         return _payload.refCountedStore.isInitialized ? _length : 0;
222     }
223 
224 /**
225 Returns the _capacity of the heap, which is the length of the
226 underlying store (if the store is a range) or the _capacity of the
227 underlying store (if the store is a container).
228      */
capacityBinaryHeap229     @property size_t capacity()
230     {
231         if (!_payload.refCountedStore.isInitialized) return 0;
232         static if (is(typeof(_store.capacity) : size_t))
233         {
234             return _store.capacity;
235         }
236         else
237         {
238             return _store.length;
239         }
240     }
241 
242 /**
243 Returns a copy of the _front of the heap, which is the largest element
244 according to $(D less).
245      */
246     @property ElementType!Store front()
247     {
248         enforce(!empty, "Cannot call front on an empty heap.");
249         return _store.front;
250     }
251 
252 /**
253 Clears the heap by detaching it from the underlying store.
254      */
clearBinaryHeap255     void clear()
256     {
257         _payload = _payload.init;
258     }
259 
260 /**
261 Inserts $(D value) into the store. If the underlying store is a range
262 and $(D length == capacity), throws an exception.
263      */
264     size_t insert(ElementType!Store value)
265     {
266         static if (is(typeof(_store.insertBack(value))))
267         {
268             _payload.refCountedStore.ensureInitialized();
269             if (length == _store.length)
270             {
271                 // reallocate
272                 _store.insertBack(value);
273             }
274             else
275             {
276                 // no reallocation
277                 _store[_length] = value;
278             }
279         }
280         else
281         {
282             import std.traits : isDynamicArray;
283             static if (isDynamicArray!Store)
284             {
285                 if (length == _store.length)
286                     _store.length = (length < 6 ? 8 : length * 3 / 2);
287                 _store[_length] = value;
288             }
289             else
290             {
291                 // can't grow
292                 enforce(length < _store.length,
293                         "Cannot grow a heap created over a range");
294             }
295         }
296 
297         // sink down the element
298         for (size_t n = _length; n; )
299         {
300             auto parentIdx = (n - 1) / 2;
301             if (!comp(_store[parentIdx], _store[n])) break; // done!
302             // must swap and continue
303             _store.swapAt(parentIdx, n);
304             n = parentIdx;
305         }
306         ++_length;
307         debug(BinaryHeap) assertValid();
308         return 1;
309     }
310 
311 /**
312 Removes the largest element from the heap.
313      */
removeFrontBinaryHeap314     void removeFront()
315     {
316         enforce(!empty, "Cannot call removeFront on an empty heap.");
317         if (_length > 1)
318         {
319             auto t1 = _store[].moveFront();
320             auto t2 = _store[].moveAt(_length - 1);
321             _store.front = move(t2);
322             _store[_length - 1] = move(t1);
323         }
324         --_length;
325         percolate(_store[], 0, _length);
326     }
327 
328     /// ditto
329     alias popFront = removeFront;
330 
331 /**
332 Removes the largest element from the heap and returns a copy of
333 it. The element still resides in the heap's store. For performance
334 reasons you may want to use $(D removeFront) with heaps of objects
335 that are expensive to copy.
336      */
337     ElementType!Store removeAny()
338     {
339         removeFront();
340         return _store[_length];
341     }
342 
343 /**
344 Replaces the largest element in the store with $(D value).
345      */
346     void replaceFront(ElementType!Store value)
347     {
348         // must replace the top
349         assert(!empty, "Cannot call replaceFront on an empty heap.");
350         _store.front = value;
351         percolate(_store[], 0, _length);
352         debug(BinaryHeap) assertValid();
353     }
354 
355 /**
356 If the heap has room to grow, inserts $(D value) into the store and
357 returns $(D true). Otherwise, if $(D less(value, front)), calls $(D
358 replaceFront(value)) and returns again $(D true). Otherwise, leaves
359 the heap unaffected and returns $(D false). This method is useful in
360 scenarios where the smallest $(D k) elements of a set of candidates
361 must be collected.
362      */
363     bool conditionalInsert(ElementType!Store value)
364     {
365         _payload.refCountedStore.ensureInitialized();
366         if (_length < _store.length)
367         {
368             insert(value);
369             return true;
370         }
371 
372         assert(!_store.empty, "Cannot replace front of an empty heap.");
373         if (!comp(value, _store.front)) return false; // value >= largest
374         _store.front = value;
375 
376         percolate(_store[], 0, _length);
377         debug(BinaryHeap) assertValid();
378         return true;
379     }
380 
381 /**
382 Swapping is allowed if the heap is full. If $(D less(value, front)), the
383 method exchanges store.front and value and returns $(D true). Otherwise, it
384 leaves the heap unaffected and returns $(D false).
385      */
386     bool conditionalSwap(ref ElementType!Store value)
387     {
388         _payload.refCountedStore.ensureInitialized();
389         assert(_length == _store.length);
390         assert(!_store.empty, "Cannot swap front of an empty heap.");
391 
392         if (!comp(value, _store.front)) return false; // value >= largest
393 
394         import std.algorithm.mutation : swap;
395         swap(_store.front, value);
396 
397         percolate(_store[], 0, _length);
398         debug(BinaryHeap) assertValid();
399 
400         return true;
401     }
402 }
403 
404 /// Example from "Introduction to Algorithms" Cormen et al, p 146
405 @system unittest
406 {
407     import std.algorithm.comparison : equal;
408     int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ];
409     auto h = heapify(a);
410     // largest element
411     assert(h.front == 16);
412     // a has the heap property
413     assert(equal(a, [ 16, 14, 10, 8, 7, 9, 3, 2, 4, 1 ]));
414 }
415 
416 /// $(D BinaryHeap) implements the standard input range interface, allowing
417 /// lazy iteration of the underlying range in descending order.
418 @system unittest
419 {
420     import std.algorithm.comparison : equal;
421     import std.range : take;
422     int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7];
423     auto top5 = heapify(a).take(5);
424     assert(top5.equal([16, 14, 10, 9, 8]));
425 }
426 
427 /**
428 Convenience function that returns a $(D BinaryHeap!Store) object
429 initialized with $(D s) and $(D initialSize).
430  */
431 BinaryHeap!(Store, less) heapify(alias less = "a < b", Store)(Store s,
432         size_t initialSize = size_t.max)
433 {
434 
435     return BinaryHeap!(Store, less)(s, initialSize);
436 }
437 
438 ///
439 @system unittest
440 {
441     import std.conv : to;
442     import std.range.primitives;
443     {
444         // example from "Introduction to Algorithms" Cormen et al., p 146
445         int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ];
446         auto h = heapify(a);
447         h = heapify!"a < b"(a);
448         assert(h.front == 16);
449         assert(a == [ 16, 14, 10, 8, 7, 9, 3, 2, 4, 1 ]);
450         auto witness = [ 16, 14, 10, 9, 8, 7, 4, 3, 2, 1 ];
451         for (; !h.empty; h.removeFront(), witness.popFront())
452         {
453             assert(!witness.empty);
454             assert(witness.front == h.front);
455         }
456         assert(witness.empty);
457     }
458     {
459         int[] a = [ 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 ];
460         int[] b = new int[a.length];
461         BinaryHeap!(int[]) h = BinaryHeap!(int[])(b, 0);
foreach(e;a)462         foreach (e; a)
463         {
464             h.insert(e);
465         }
466         assert(b == [ 16, 14, 10, 8, 7, 3, 9, 1, 4, 2 ], to!string(b));
467     }
468 }
469 
470 @system unittest
471 {
472     // Test range interface.
473     import std.algorithm.comparison : equal;
474     int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7];
475     auto h = heapify(a);
476     static assert(isInputRange!(typeof(h)));
477     assert(h.equal([16, 14, 10, 9, 8, 7, 4, 3, 2, 1]));
478 }
479 
480 @system unittest // 15675
481 {
482     import std.container.array : Array;
483 
484     Array!int elements = [1, 2, 10, 12];
485     auto heap = heapify(elements);
486     assert(heap.front == 12);
487 }
488 
489 @system unittest // 16072
490 {
491     auto q = heapify!"a > b"([2, 4, 5]);
492     q.insert(1);
493     q.insert(6);
494     assert(q.front == 1);
495 
496     // test more multiple grows
497     int[] arr;
498     auto r = heapify!"a < b"(arr);
499     foreach (i; 0 .. 100)
500         r.insert(i);
501 
502     assert(r.front == 99);
503 }
504 
505 @system unittest
506 {
507     import std.algorithm.comparison : equal;
508     int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7];
509     auto heap = heapify(a);
510     auto dup = heap.dup();
511     assert(dup.equal([16, 14, 10, 9, 8, 7, 4, 3, 2, 1]));
512 }
513 
514 @safe unittest
515 {
516     static struct StructWithoutDup
517     {
518         int[] a;
dupStructWithoutDup519         @disable StructWithoutDup dup()
520         {
521             StructWithoutDup d;
522             return d;
523         }
524         alias a this;
525     }
526 
527     // Assert Binary heap can be created when Store doesn't have dup
528     // if dup is not used.
529     assert(__traits(compiles, ()
530         {
531             auto s = StructWithoutDup([1,2]);
532             auto h = heapify(s);
533         }));
534 
535     // Assert dup can't be used on BinaryHeaps when Store doesn't have dup
536     assert(!__traits(compiles, ()
537         {
538             auto s = StructWithoutDup([1,2]);
539             auto h = heapify(s);
540             h.dup();
541         }));
542 }
543 
544 @safe unittest
545 {
546     static struct StructWithDup
547     {
548         int[] a;
dupStructWithDup549         StructWithDup dup()
550         {
551             StructWithDup d;
552             return d;
553         }
554         alias a this;
555     }
556 
557     // Assert dup can be used on BinaryHeaps when Store has dup
558     assert(__traits(compiles, ()
559         {
560             auto s = StructWithDup([1, 2]);
561             auto h = heapify(s);
562             h.dup();
563         }));
564 }
565 
566 @system unittest
567 {
568     import std.algorithm.comparison : equal;
569     import std.internal.test.dummyrange;
570 
571     alias RefRange = DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random);
572 
573     RefRange a;
574     RefRange b;
575     a.reinit();
576     b.reinit();
577 
578     auto heap = heapify(a);
foreach(ref elem;b)579     foreach (ref elem; b)
580     {
581         heap.conditionalSwap(elem);
582     }
583 
584     assert(equal(heap, [ 5, 5, 4, 4, 3, 3, 2, 2, 1, 1]));
585     assert(equal(b, [10, 9, 8, 7, 6, 6, 7, 8, 9, 10]));
586 }
587 
588 @system unittest // Issue 17314
589 {
590     import std.algorithm.comparison : equal;
591     int[] a = [5];
592     auto heap = heapify(a);
593     heap.insert(6);
594     assert(equal(heap, [6, 5]));
595 }
596