Completed
Pull Request — master (#30)
by Dan
02:28
created

PriorityQueue::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 0
1
<?php
2
namespace Ds;
3
4
use UnderflowException;
5
6
/**
7
 * PriorityNode
8
 *
9
 * @package Ds
10
 */
11
final class PriorityNode
12
{
13
    /**
14
     * @var mixed
15
     */
16
    public $value;
17
18
    /**
19
     * @var int
20
     */
21
    public $priority;
22
23
    /**
24
     * @var int
25
     */
26
    public $stamp;
27
28
    /**
29
     * @param mixed $value
30
     * @param int   $priority
31
     * @param int   $stamp
32
     */
33
    public function __construct($value, int $priority, int $stamp)
34
    {
35
        $this->value    = $value;
36
        $this->priority = $priority;
37
        $this->stamp    = $stamp;
38
    }
39
}
40
41
/**
42
 * PriorityQueue
43
 *
44
 * @package Ds
45
 */
46
final class PriorityQueue implements \IteratorAggregate, Collection
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
47
{
48
    use Traits\Collection;
49
    use Traits\SquaredCapacity;
50
51
    /**
52
     * @var int
53
     */
54
    const MIN_CAPACITY = 8;
55
56
    /**
57
     * @var array
58
     */
59
    private $heap;
60
61
    /**
62
     * @var int
63
     */
64
    private $stamp;
65
66
    /**
67
     * Initializes a new priority queue.
68
     */
69
    public function __construct()
70
    {
71
        $this->reset();
72
    }
73
74
    /**
75
     * @inheritDoc
76
     */
77
    public function clear()
78
    {
79
        $this->reset();
80
    }
81
    
82
    /**
83
     * Reset the PriorityQueue to an initialised state.
84
     */
85
    private reset()
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION
Loading history...
86
    {
87
        $this->heap     = [];
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $this.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
88
        $this->stamp    = 0;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $this.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
89
        $this->capacity = self::MIN_CAPACITY;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $this.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
90
    }
91
92
    /**
93
     * @inheritDoc
94
     */
95
    public function copy()
96
    {
97
        $copy = new PriorityQueue();
98
        
99
        $copy->heap     = $this->heap;
100
        $copy->capacity = $this->capacity;
101
102
        return $copy;
103
    }
104
105
    /**
106
     * @inheritDoc
107
     */
108
    public function count(): int
109
    {
110
        return count($this->heap);
111
    }
112
113
    /**
114
     * Returns the value with the highest priority in the priority queue.
115
     *
116
     * @return mixed
117
     *
118
     * @throw UnderflowException
119
     */
120
    public function peek()
121
    {
122
        if ($this->isEmpty()) {
123
            throw new UnderflowException();
124
        }
125
126
        return $this->heap[0]->value;
127
    }
128
129
    /**
130
     * Left
131
     *
132
     * @param int $index
133
     *
134
     * @return int
135
     */
136
    private function left(int $index): int
137
    {
138
        return ($index * 2) + 1;
139
    }
140
141
    /**
142
     * Right
143
     *
144
     * @param int $index
145
     *
146
     * @return int
147
     */
148
    private function right(int $index): int
149
    {
150
        return ($index * 2) + 2;
151
    }
152
153
    /**
154
     * Parent
155
     *
156
     * @param int $index
157
     *
158
     * @return int
159
     */
160
    private function parent(int $index): int
161
    {
162
        return ($index - 1) / 2;
163
    }
164
165
    /**
166
     * Compare
167
     *
168
     * @param int $a
169
     * @param int $b
170
     *
171
     * @return integer
172
     */
173
    private function compare(int $a, int $b)
174
    {
175
        $x = $this->heap[$a];
176
        $y = $this->heap[$b];
177
178
        // Compare priority, using insertion stamp as fallback.
179
        return ($x->priority <=> $y->priority) ?: ($y->stamp <=> $x->stamp);
180
    }
181
182
    /**
183
     * Swap
184
     *
185
     * @param int $a
186
     * @param int $b
187
     */
188
    private function swap(int $a, int $b)
189
    {
190
        $temp           = $this->heap[$a];
191
        $this->heap[$a] = $this->heap[$b];
192
        $this->heap[$b] = $temp;
193
    }
194
195
    /**
196
     * Get Largest Leaf
197
     *
198
     * @param int $parent
199
     *
200
     * @return int
201
     */
202
    private function getLargestLeaf(int $parent)
203
    {
204
        $left  = $this->left($parent);
205
        $right = $left + 1;
206
207
        if ($right < count($this->heap) && $this->compare($left, $right) < 0) {
208
            return $right;
209
        }
210
211
        return $left;
212
    }
213
214
    /**
215
     * Sift Down
216
     *
217
     * @param int $node
218
     */
219
    private function siftDown(int $node)
220
    {
221
        $last = floor(count($this->heap) / 2);
222
223
        for ($parent = $node; $parent < $last; $parent = $leaf) {
224
225
            // Determine the largest leaf to potentially swap with the parent.
226
            $leaf = $this->getLargestLeaf($parent);
227
228
            // Done if the parent is not greater than its largest leaf
229
            if ($this->compare($parent, $leaf) > 0) {
230
                break;
231
            }
232
233
            $this->swap($parent, $leaf);
234
        }
235
    }
236
237
    /**
238
     * Set Root
239
     *
240
     * @param PriorityNode $node
241
     */
242
    private function setRoot(PriorityNode $node)
243
    {
244
        $this->heap[0] = $node;
245
    }
246
247
    /**
248
     * Get Root
249
     *
250
     * @return PriorityNode
251
     */
252
    private function getRoot(): PriorityNode
253
    {
254
        return $this->heap[0];
255
    }
256
257
    /**
258
     * Returns and removes the value with the highest priority in the queue.
259
     *
260
     * @return mixed
261
     */
262
    public function pop()
263
    {
264
        if ($this->isEmpty()) {
265
            throw new UnderflowException();
266
        }
267
268
        // Last leaf of the heap to become the new root.
269
        $leaf = array_pop($this->heap);
270
271
        if (empty($this->heap)) {
272
            return $leaf->value;
273
        }
274
275
        // Cache the current root value to return before replacing with next.
276
        $value = $this->getRoot()->value;
277
278
        // Replace the root, then sift down.
279
        $this->setRoot($leaf);
280
        $this->siftDown(0);
281
        $this->adjustCapacity();
282
283
        return $value;
284
    }
285
286
    /**
287
     * Sift Up
288
     *
289
     * @param int $leaf
290
     */
291
    private function siftUp(int $leaf)
292
    {
293
        for (; $leaf > 0; $leaf = $parent) {
294
            $parent = $this->parent($leaf);
295
296
            // Done when parent priority is greater.
297
            if ($this->compare($leaf, $parent) < 0) {
298
                break;
299
            }
300
301
            $this->swap($parent, $leaf);
302
        }
303
    }
304
305
    /**
306
     * Pushes a value into the queue, with a specified priority.
307
     *
308
     * @param mixed $value
309
     * @param int   $priority
310
     */
311
    public function push($value, int $priority)
312
    {
313
        $this->adjustCapacity();
314
315
        // Add new leaf, then sift up to maintain heap,
316
        $this->heap[] = new PriorityNode($value, $priority, $this->stamp++);
317
        $this->siftUp(count($this->heap) - 1);
318
    }
319
320
    /**
321
     * @inheritDoc
322
     */
323
    public function toArray(): array
324
    {
325
        $heap  = $this->heap;
326
        $array = [];
327
328
        while ( ! $this->isEmpty()) {
329
            $array[] = $this->pop();
330
        }
331
332
        $this->heap = $heap;
333
        return $array;
334
    }
335
336
    /**
337
     * Get iterator
338
     */
339
    public function getIterator()
340
    {
341
        while ( ! $this->isEmpty()) {
342
            yield $this->pop();
343
        }
344
    }
345
}
346