Passed
Push — 1.x ( 6f1130...d5fcaf )
by Ulises Jeremias
03:44 queued 01:20
created

DoublyLinkedList::copyFromContext()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
1
<?php namespace Mbh\Collection;
2
3
/**
4
 * MBHFramework
5
 *
6
 * @link      https://github.com/MBHFramework/mbh-framework
7
 * @copyright Copyright (c) 2017 Ulises Jeremias Cornejo Fandos
8
 * @license   https://github.com/MBHFramework/mbh-framework/blob/master/LICENSE (MIT License)
9
 */
10
11
use Mbh\Collection\Interfaces\Functional as FunctionalInterface;
12
use Mbh\Collection\Interfaces\Sequenceable as SequenceableInterface;
13
use Mbh\Collection\Internal\LinkedDataNode;
14
use Mbh\Collection\Internal\LinkedTerminalNode;
15
use Mbh\Interfaces\Allocated as AllocatedInterface;
16
use Mbh\Traits\Capacity;
17
use Mbh\Traits\EmptyGuard;
18
use OutOfBoundsException;
19
use Exception;
20
21
/**
22
 * The DoublyLinkedList
23
 *
24
 *
25
 *
26
 * @package structures
27
 * @author Ulises Jeremias Cornejo Fandos <[email protected]>
28
 */
29
final class DoublyLinkedList implements AllocatedInterface, FunctionalInterface, SequenceableInterface
30
{
31
    use Traits\Collection;
32
    use Traits\Functional;
33
    use Capacity;
34
    use EmptyGuard;
35
36
    const MIN_CAPACITY = 8.0;
37
38
    private $head;
39
    private $tail;
40
    private $size = 0;
41
    private $current;
42
    private $offset = -1;
43
44
    public function __construct()
45
    {
46
        $this->head = $head = new LinkedTerminalNode();
47
        $this->tail = $tail = new LinkedTerminalNode();
48
49
        $head->setNext($tail);
50
        $tail->setPrev($head);
51
52
        $this->current = $this->head;
53
    }
54
55
    public function isEmpty()
56
    {
57
        return $this->size === 0;
58
    }
59
60
    public function push(...$values)
61
    {
62
        foreach ($values as $key => $value) {
63
            $this->insertBetween($this->tail->prev(), $this->tail, $value);
64
            $this->offset = $this->size - 1;
65
        }
66
    }
67
68
    public function unshift($value)
69
    {
70
        $this->insertBetween($this->head, $this->head->next(), $value);
71
        $this->offset = 0;
72
    }
73
74
    public function pop()
75
    {
76
        $this->emptyGuard(__METHOD__);
77
78
        $n = $this->seekTail();
79
        $this->removeNode($n);
80
81
        return $n->value();
82
    }
83
84
    public function shift()
85
    {
86
        $this->emptyGuard(__METHOD__);
87
88
        $n = $this->seekHead();
89
        $this->removeNode($n);
90
91
        return $n->value();
92
    }
93
94
    public function first()
95
    {
96
        $this->emptyGuard(__METHOD__);
97
        return $this->seekHead()->value();
98
    }
99
100
    public function last()
101
    {
102
        $this->emptyGuard(__METHOD__);
103
        return $this->seekTail()->value();
104
    }
105
106
    /**
107
     * @link http://php.net/manual/en/countable.count.php
108
     * @return int
109
     */
110
    public function count()
111
    {
112
        return $this->size;
113
    }
114
115
    /**
116
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
117
     * @param int $offset
118
     * @return boolean
119
     */
120
    public function offsetExists($offset)
121
    {
122
        $index = $this->intGuard($offset);
123
        return $index >= 0 && $index < $this->count();
124
    }
125
126
    /**
127
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
128
     * @param int $offset
129
     * @return mixed
130
     * @throws OutOfBoundsException
131
     */
132
    public function offsetGet($offset)
133
    {
134
        $n = $this->guardedSeek($offset, __METHOD__);
135
        return $n->value();
136
    }
137
138
    /**
139
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
140
     * @param int|null $offset
141
     * @param mixed $value
142
     * @return void
143
     * @throws OutOfBoundsException
144
     */
145
    public function offsetSet($offset, $value)
146
    {
147
        if ($offset === null) {
148
            $this->push($value);
149
            return;
150
        }
151
        $n = $this->guardedSeek($offset, __METHOD__);
152
        $n->setValue($value);
153
    }
154
155
    /**
156
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
157
     * @param int $offset
158
     * @return void
159
     */
160
    public function offsetUnset($offset)
161
    {
162
        $index = $this->intGuard($offset);
163
        if ($this->offsetExists($index)) {
164
            $n = $this->seekTo($index);
165
            $this->removeNode($n);
166
            $this->current = $n->prev();
167
            $this->offset--;
168
        }
169
    }
170
171
    public function insert(int $index, ...$values)
172
    {
173
        foreach ($values as &$value) {
174
            $this->insertBefore($index++, $value);
175
        }
176
    }
177
178
    /**
179
     * @param int $position
180
     * @param mixed $value
181
     * @return void
182
     *
183
     * @throws OutOfBoundsException
184
     */
185
    public function insertBefore(int $position, mixed $value)
0 ignored issues
show
Bug introduced by
The type Mbh\Collection\mixed was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
186
    {
187
        $n = $this->guardedSeek($position, __METHOD__);
188
        $this->insertBetween($n->prev(), $n, $value);
189
        $this->current = $this->current->next();
190
        $this->offset++;
191
    }
192
193
    /**
194
     * @param int $position
195
     * @param mixed $value
196
     * @return void
197
     *
198
     * @throws OutOfBoundsException
199
     */
200
    public function insertAfter(int $position, mixed $value)
201
    {
202
        $n = $this->guardedSeek($position, __METHOD__);
203
        $this->insertBetween($n, $n->next(), $value);
204
        $this->current = $this->current->prev();
205
    }
206
207
    /**
208
     * @link http://php.net/manual/en/iterator.current.php
209
     * @return mixed
210
     */
211
    public function current()
212
    {
213
        return $this->current->value();
0 ignored issues
show
Bug introduced by
The method value() does not exist on Mbh\Collection\Internal\LinkedTerminalNode. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

213
        return $this->current->/** @scrutinizer ignore-call */ value();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
214
    }
215
216
    /**
217
     * @link http://php.net/manual/en/iterator.next.php
218
     * @return void
219
     */
220
    public function next()
221
    {
222
        $this->forward();
223
    }
224
225
    /**
226
     * @return void
227
     */
228
    public function prev()
229
    {
230
        $this->backward();
231
    }
232
233
    /**
234
     * @link http://php.net/manual/en/iterator.key.php
235
     * @return mixed
236
     */
237
    public function key()
238
    {
239
        return $this->offset;
240
    }
241
242
    /**
243
     * @link http://php.net/manual/en/iterator.valid.php
244
     * @return boolean
245
     */
246
    public function valid()
247
    {
248
        return $this->current instanceof LinkedDataNode;
249
    }
250
251
    /**
252
     * @link http://php.net/manual/en/iterator.rewind.php
253
     * @return void
254
     */
255
    public function rewind()
256
    {
257
        $this->current = $this->head;
258
        $this->offset = -1;
259
        $this->forward();
260
    }
261
262
    /**
263
     * @link http://php.net/manual/en/seekableiterator.seek.php
264
     * @param int $position
265
     * @return mixed
266
     * @throws OutOfBoundsException
267
     * @throws Exception
268
     */
269
    public function seek($position)
270
    {
271
        $index = $this->intGuard($position);
272
        $this->indexGuard($index, __METHOD__);
273
274
        if ($index === 0) {
275
            return $this->seekHead()->value();
276
        } elseif ($index === $this->size - 1) {
277
            return $this->seekTail()->value();
278
        }
279
280
        return $this->seekTo($index)->value();
281
    }
282
283
    /**
284
     * @param mixed $i
285
     * @return int
286
     * @throws Exception
287
     */
288
    private function intGuard($i)
289
    {
290
        if (filter_var($i, FILTER_VALIDATE_INT) === false) {
291
            throw new Exception;
292
        }
293
294
        return (int) $i;
295
    }
296
297
298
    /**
299
     * @param $value
300
     * @param callable $f
301
     * @return int
302
     */
303
    public function indexOf($value, callable $f = null)
304
    {
305
        $equal = $f;
306
307
        $filter = $this->filter(function ($item) use ($equal, $value) {
308
            return $equal($item, $value);
309
        });
310
311
        foreach ($filter as $key => $value) {
312
            return $key;
313
        }
314
315
        return -1;
316
    }
317
318
    /**
319
     * @param $value
320
     * @param callable $f [optional]
321
     * @return bool
322
     */
323
    public function contains($value, callable $f = null)
324
    {
325
        return $this->indexOf($value, $f) >= 0;
326
    }
327
328
    /**
329
     * Extract the elements after the first of a list, which must be non-empty.
330
     * @return LinkedList
0 ignored issues
show
Bug introduced by
The type Mbh\Collection\LinkedList was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
331
     * @throws EmptyException
332
     */
333
    public function tail()
334
    {
335
        $this->emptyGuard(__METHOD__);
336
        return $this->copyFromContext($this->head->next()->next());
337
    }
338
339
    public function __clone()
340
    {
341
        $list = $this->copyFromContext($this->head->next());
342
343
        $this->head = $list->head;
344
        $this->tail = $list->tail;
345
346
        $this->current = $this->head;
347
        $this->offset = -1;
348
349
        $this->size = $list->size;
350
    }
351
352
    private function copyFromContext(LinkedNode $context)
0 ignored issues
show
Bug introduced by
The type Mbh\Collection\LinkedNode was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
353
    {
354
        $list = new static();
355
356
        for ($n = $context; $n !== $this->tail; $n = $n->next()) {
357
            /**
358
             * @var LinkedDataNode $n
359
             */
360
            $list->push($n->value());
361
        }
362
363
        return $list;
364
    }
365
366
    private function insertBetween(LinkedNode $a, LinkedNode $b, mixed $value)
367
    {
368
        $n = new LinkedDataNode($value);
369
370
        $a->setNext($n);
371
        $b->setPrev($n);
372
373
        $n->setPrev($a);
374
        $n->setNext($b);
375
376
        $this->current = $n;
377
        $this->size++;
378
    }
379
380
    private function forward()
381
    {
382
        $this->current = $this->current->next();
383
        $this->offset++;
384
    }
385
386
    private function backward()
387
    {
388
        $this->current = $this->current->prev();
389
        $this->offset--;
390
    }
391
392
    private function removeNode(LinkedNode $n)
393
    {
394
        $prev = $n->prev();
395
        $next = $n->next();
396
397
        $prev->setNext($next);
398
        $next->setPrev($prev);
399
400
        $this->size--;
401
    }
402
403
    /**
404
     * @return LinkedDataNode
405
     */
406
    private function seekTail()
407
    {
408
        $this->offset = $this->size - 1;
409
        return $this->current = $this->tail->prev();
410
    }
411
412
    /**
413
     * @return LinkedDataNode
414
     */
415
    private function seekHead()
416
    {
417
        $this->offset = 0;
418
        return $this->current = $this->head->next();
419
    }
420
421
    /**
422
     * @param $offset
423
     * @return LinkedDataNode
424
     */
425
    private function seekTo($offset)
426
    {
427
        $n = abs($diff = $this->offset - $offset);
428
        $action = ($diff < 0) ? 'forward' : 'backward';
429
430
        for ($i = 0; $i < $n; $i++) {
431
            $this->$action();
432
        }
433
434
        return $this->current;
435
    }
436
437
    private function indexGuard($offset, $method)
438
    {
439
        if (!$this->offsetExists($offset)) {
440
            throw new OutOfBoundsException(
441
                "{$method} was called with invalid index: {$offset}"
442
            );
443
        }
444
    }
445
446
    private function guardedSeek($index, $method)
447
    {
448
        $index = $this->intGuard($index);
449
        $this->indexGuard($index, $method);
450
451
        return $this->seekTo($index);
452
    }
453
}
454