Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.78% covered (success)
98.78%
81 / 82
94.44% covered (success)
94.44%
17 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
Heap
98.78% covered (success)
98.78%
81 / 82
94.44% covered (success)
94.44%
17 / 18
35
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 insort
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 push
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 pop
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 peek
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 contains
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 replace
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 pushpop
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 heapify
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 update
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 getNLargest
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getNSmallest
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 siftDown
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 siftUp
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 clear
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isEmpty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 size
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Stdlib\Base
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Stdlib\Base;
16
17/**
18 * Heap class.
19 *
20 * @package phpOMS\Stdlib\Base
21 * @license OMS License 2.0
22 * @link    https://jingga.app
23 * @since   1.0.0
24 */
25class Heap
26{
27    /**
28     * Comparison function
29     *
30     * @var \Closure
31     * @since 1.0.0
32     */
33    private \Closure $compare;
34
35    /**
36     * Heap items
37     *
38     * @var array<int, HeapItemInterface>
39     * @since 1.0.0
40     */
41    private array $nodes = [];
42
43    /**
44     * Constructor.
45     *
46     * @param null|\Closure $compare Compare function
47     *
48     * @since 1.0.0
49     */
50    public function __construct(\Closure $compare = null)
51    {
52        $this->compare = $compare ?? function($a, $b) {
53            return $a <=> $b;
54        };
55    }
56
57    /**
58     * Insert item into sorted heap at correct position
59     *
60     * @param HeapItemInterface $x  Element to insert
61     * @param int               $lo Lower bound
62     *
63     * @return void
64     *
65     * @since 1.0.0
66     */
67    public function insort(HeapItemInterface $x, int $lo = 0) : void
68    {
69        $hi = \count($this->nodes);
70
71        while ($lo < $hi) {
72            $mid = (int) (($lo + $hi) / 2);
73            if (($this->compare)($x, $this->nodes[$mid]) < 0) {
74                $hi = $mid;
75            } else {
76                $lo = $mid + 1;
77            }
78        }
79
80        \array_splice($this->nodes, $lo, 0, [$x]);
81    }
82
83    /**
84     * Push item onto the heap
85     *
86     * @param HeapItemInterface $item Item to add to the heap
87     *
88     * @return void;
89     *
90     * @since 1.0.0
91     */
92    public function push(HeapItemInterface $item) : void
93    {
94        $this->nodes[] = $item;
95        $this->siftDown(0, \count($this->nodes) - 1);
96    }
97
98    /**
99     * Pop the smallest item off the heap
100     *
101     * @return null|HeapItemInterface
102     *
103     * @since 1.0.0
104     */
105    public function pop() : ?HeapItemInterface
106    {
107        $last = \array_pop($this->nodes);
108        if ($last === null) {
109            return null;
110        }
111
112        if (empty($this->nodes)) {
113            return $last;
114        }
115
116        /** @var HeapItemInterface $last */
117        $item           = $this->nodes[0];
118        $this->nodes[0] = $last;
119        $this->siftUp(0);
120
121        return $item;
122    }
123
124    /**
125     * Get first item without popping
126     *
127     * @return HeapItemInterface
128     *
129     * @since 1.0.0
130     */
131    public function peek() : HeapItemInterface
132    {
133        return $this->nodes[0];
134    }
135
136    /**
137     * Contains item?
138     *
139     * @param HeapItemInterface $item Item to check
140     *
141     * @return bool
142     *
143     * @since 1.0.0
144     */
145    public function contains(HeapItemInterface $item) : bool
146    {
147        foreach ($this->nodes as $node) {
148            if ($item->isEqual($node)) {
149                return true;
150            }
151        }
152
153        return false;
154    }
155
156    /**
157     * Pop a item and push a new one (replace with a new one)
158     *
159     * @param HeapItemInterface $new New item
160     *
161     * @return HeapItemInterface popped item
162     *
163     * @since 1.0.0
164     */
165    public function replace(HeapItemInterface $new) : HeapItemInterface
166    {
167        $old            = $this->nodes[0];
168        $this->nodes[0] = $new;
169        $this->siftUp(0);
170
171        return $old;
172    }
173
174    /**
175     * Push item and pop one
176     *
177     * @param HeapItemInterface $item New item
178     *
179     * @return HeapItemInterface popped item
180     *
181     * @since 1.0.0
182     */
183    public function pushpop(HeapItemInterface $item) : HeapItemInterface
184    {
185        if (!empty($this->nodes) && ($this->compare)($this->nodes[0], $item) < 0) {
186            $temp           = $item;
187            $item           = $this->nodes[0];
188            $this->nodes[0] = $temp;
189            $this->siftUp(0);
190        }
191
192        return $item;
193    }
194
195    /**
196     * Turn list into heap
197     *
198     * @param array $list Item list
199     *
200     * @return void
201     *
202     * @since 1.0.0
203     */
204    public function heapify(array $list) : void
205    {
206        $this->nodes = $list;
207
208        for ($i = (int) (\count($this->nodes) / 2); $i > -1; --$i) {
209            $this->siftUp($i);
210        }
211    }
212
213    /**
214     * Update the position of a item
215     *
216     * This is called after changing an item
217     *
218     * @param HeapItemInterface $item Item to update
219     *
220     * @return bool
221     *
222     * @since 1.0.0
223     */
224    public function update(HeapItemInterface $item) : bool
225    {
226        $pos = null;
227        foreach ($this->nodes as $key => $node) {
228            if ($item->isEqual($node)) {
229                $pos = $key;
230                break;
231            }
232        }
233
234        if ($pos === null) {
235            return false;
236        }
237
238        $this->siftDown(0, $pos);
239        $this->siftUp($pos);
240
241        return true;
242    }
243
244    /**
245     * Get n largest items
246     *
247     * @param int $n Number of items
248     *
249     * @return array
250     *
251     * @since 1.0.0
252     */
253    public function getNLargest(int $n) : array
254    {
255        $nodes = $this->nodes;
256        \uasort($nodes, $this->compare);
257
258        return \array_slice(\array_reverse($nodes), 0, $n);
259    }
260
261    /**
262     * Get n smalles items
263     *
264     * @param int $n Number of items
265     *
266     * @return array
267     *
268     * @since 1.0.0
269     */
270    public function getNSmallest(int $n) : array
271    {
272        $nodes = $this->nodes;
273        \uasort($nodes, $this->compare);
274
275        return \array_slice($nodes, 0, $n);
276    }
277
278    /**
279     * Down shift
280     *
281     * @param int $start Start index
282     * @param int $pos   Pos of the pivot item
283     *
284     * @return void
285     *
286     * @since 1.0.0
287     */
288    private function siftDown(int $start, int $pos) : void
289    {
290        $item = $this->nodes[$pos];
291        while ($pos > $start) {
292            $pPos   = ($pos - 1) >> 1;
293            $parent = $this->nodes[$pPos];
294
295            if (($this->compare)($item, $parent) < 0) {
296                $this->nodes[$pos] = $parent;
297                $pos               = $pPos;
298
299                continue;
300            }
301
302            break;
303        }
304
305        $this->nodes[$pos] = $item;
306    }
307
308    /**
309     * Up shift
310     *
311     * @param int $pos Pos of the pivot item
312     *
313     * @return void
314     *
315     * @since 1.0.0
316     */
317    private function siftUp(int $pos) : void
318    {
319        $ePos = \count($this->nodes);
320        $sPos = $pos;
321        $item = $this->nodes[$pos];
322        $cPos = 2 * $pos + 1;
323
324        while ($cPos < $ePos) {
325            $rPos = $cPos + 1;
326
327            if ($rPos < $ePos && ($this->compare)($this->nodes[$cPos], $this->nodes[$rPos]) > -1) {
328                $cPos = $rPos;
329            }
330
331            $this->nodes[$pos] = $this->nodes[$cPos];
332            $pos               = $cPos;
333            $cPos              = 2 * $pos + 1;
334        }
335
336        $this->nodes[$pos] = $item;
337        $this->siftDown($sPos, $pos);
338    }
339
340    /**
341     * Clear heap
342     *
343     * @return void
344     *
345     * @since 1.0.0
346     */
347    public function clear() : void
348    {
349        $this->nodes = [];
350    }
351
352    /**
353     * Is heap empty?
354     *
355     * @return bool
356     *
357     * @since 1.0.0
358     */
359    public function isEmpty() : bool
360    {
361        return empty($this->nodes);
362    }
363
364    /**
365     * Get heap size
366     *
367     * @return int
368     *
369     * @since 1.0.0
370     */
371    public function size() : int
372    {
373        return \count($this->nodes);
374    }
375
376    /**
377     * Get heap array
378     *
379     * @return array
380     *
381     * @since 1.0.0
382     */
383    public function toArray() : array
384    {
385        return $this->nodes;
386    }
387}