Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.78% |
81 / 82 |
|
94.44% |
17 / 18 |
CRAP | |
0.00% |
0 / 1 |
Heap | |
98.78% |
81 / 82 |
|
94.44% |
17 / 18 |
35 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
insort | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
push | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
pop | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
3.01 | |||
peek | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
contains | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
replace | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
pushpop | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
heapify | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
update | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
getNLargest | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getNSmallest | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
siftDown | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
siftUp | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
4 | |||
clear | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isEmpty | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
size | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
toArray | |
100.00% |
1 / 1 |
|
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 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace 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 | */ |
25 | class 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 | } |