Passed
Push — 1.x ( b30111...94f19e )
by Ulises Jeremias
02:22
created

DoublyLinkedList::tail()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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\Collection as CollectionInterface;
12
use Mbh\Collection\Interfaces\Functional as FunctionalInterface;
13
use Mbh\Collection\Interfaces\Sequenceable as SequenceableInterface;
14
use Mbh\Collection\Internal\Interfaces\LinkedNode;
15
use Mbh\Collection\Internal\LinkedDataNode;
16
use Mbh\Collection\Internal\LinkedTerminalNode;
17
use Mbh\Interfaces\Allocated as AllocatedInterface;
18
use Mbh\Traits\Capacity;
19
use Mbh\Traits\EmptyGuard;
20
use Traversable;
21
use OutOfBoundsException;
22
use Exception;
23
24
/**
25
 * The DoublyLinkedList
26
 *
27
 *
28
 *
29
 * @package structures
30
 * @author Ulises Jeremias Cornejo Fandos <[email protected]>
31
 */
32
final class DoublyLinkedList implements AllocatedInterface, SequenceableInterface
33
{
34
    use Traits\Collection;
35
    use Traits\Functional;
36
    use Traits\Builder;
37
    use Capacity;
38
    use EmptyGuard;
39
40
    const MIN_CAPACITY = 8.0;
41
42
    private $head;
43
    private $tail;
44
    private $size = 0;
45
    private $current;
46
    private $offset = -1;
47
48
    /**
49
     * Create an fixed array
50
     *
51
     * @param array|Traversable $array data
52
     */
53
    public function __construct($array = null)
54
    {
55
        $this->init();
56
57
        if ($array) {
58
            $this->pushAll($array);
59
        }
60
    }
61
62
    private function init()
63
    {
64
        $this->head = $head = new LinkedTerminalNode();
65
        $this->tail = $tail = new LinkedTerminalNode();
66
67
        $head->setNext($tail);
68
        $tail->setPrev($head);
69
70
        $this->current = $this->head;
71
    }
72
73
    public function copy()
74
    {
75
        return $this->copyFromContext($this->head->next());
76
    }
77
78
    public function isEmpty(): bool
79
    {
80
        return $this->size === 0;
81
    }
82
83
    private function pushAll($values)
84
    {
85
        foreach ($values as $key => $value) {
86
            $this->insertBetween($this->tail->prev(), $this->tail, $value);
87
            $this->offset = $this->size - 1;
88
        }
89
    }
90
91
    public function push(...$values)
92
    {
93
        $this->pushAll($values);
94
    }
95
96
    public function unshift(...$values)
97
    {
98
        foreach ($values as &$value) {
99
            $this->insertBetween($this->head, $this->head->next(), $value);
100
            $this->offset = 0;
101
        }
102
    }
103
104
    public function pop()
105
    {
106
        $this->emptyGuard(__METHOD__);
107
108
        $n = $this->seekTail();
109
        $this->removeNode($n);
110
111
        return $n->value();
112
    }
113
114
    public function shift()
115
    {
116
        $this->emptyGuard(__METHOD__);
117
118
        $n = $this->seekHead();
119
        $this->removeNode($n);
120
121
        return $n->value();
122
    }
123
124
    public function first()
125
    {
126
        $this->emptyGuard(__METHOD__);
127
        return $this->seekHead()->value();
128
    }
129
130
    public function last()
131
    {
132
        $this->emptyGuard(__METHOD__);
133
        return $this->seekTail()->value();
134
    }
135
136
    /**
137
     * @link http://php.net/manual/en/countable.count.php
138
     * @return int
139
     */
140
    public function count(): int
141
    {
142
        return $this->size;
143
    }
144
145
    public function get(int $index)
146
    {
147
        return $this[$index];
148
    }
149
150
    /**
151
     * @inheritDoc
152
     */
153
    public function set(int $index, $value)
154
    {
155
        $this->offsetSet($index, $value);
156
    }
157
158
    /**
159
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
160
     * @param int $offset
161
     * @return boolean
162
     */
163
    public function offsetExists($offset)
164
    {
165
        $index = $this->intGuard($offset);
166
        return $index >= 0 && $index < $this->count();
167
    }
168
169
    /**
170
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
171
     * @param int $offset
172
     * @return mixed
173
     * @throws OutOfBoundsException
174
     */
175
    public function offsetGet($offset)
176
    {
177
        $n = $this->guardedSeek($offset, __METHOD__);
178
        return $n->value();
179
    }
180
181
    /**
182
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
183
     * @param int|null $offset
184
     * @param mixed $value
185
     * @return void
186
     * @throws OutOfBoundsException
187
     */
188
    public function offsetSet($offset, $value)
189
    {
190
        if ($offset === null) {
191
            $this->push($value);
192
            return;
193
        }
194
        $n = $this->guardedSeek($offset, __METHOD__);
195
        $n->setValue($value);
196
    }
197
198
    /**
199
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
200
     * @param int $offset
201
     * @return void
202
     */
203
    public function offsetUnset($offset)
204
    {
205
        $index = $this->intGuard($offset);
206
        if ($this->offsetExists($index)) {
207
            $n = $this->seekTo($index);
208
            $this->removeNode($n);
209
            $this->current = $n->prev();
210
            $this->offset--;
211
        }
212
    }
213
214
    public function insert(int $index, ...$values)
215
    {
216
        foreach ($values as &$value) {
217
            $this->insertBefore($index++, $value);
218
        }
219
    }
220
221
    protected function getValues(): Traversable
222
    {
223
        return SplFixedArray::fromArray($this->toArray());
0 ignored issues
show
Bug introduced by
The type Mbh\Collection\SplFixedArray was not found. Did you mean SplFixedArray? If so, make sure to prefix the type with \.
Loading history...
224
    }
225
226
    protected function setValues(Traversable $traversable)
227
    {
228
        $this->clear();
229
        $this->pushAll($traversable);
230
    }
231
232
    /**
233
     * @param int $position
234
     * @param mixed $value
235
     * @return void
236
     *
237
     * @throws OutOfBoundsException
238
     */
239
    public function insertBefore(int $position, $value)
240
    {
241
        $n = $this->guardedSeek($position, __METHOD__);
242
        $this->insertBetween($n->prev(), $n, $value);
243
        $this->current = $this->current->next();
244
        $this->offset++;
245
    }
246
247
    /**
248
     * @param int $position
249
     * @param mixed $value
250
     * @return void
251
     *
252
     * @throws OutOfBoundsException
253
     */
254
    public function insertAfter(int $position, $value)
255
    {
256
        $n = $this->guardedSeek($position, __METHOD__);
257
        $this->insertBetween($n, $n->next(), $value);
258
        $this->current = $this->current->prev();
259
    }
260
261
    /**
262
     * @link http://php.net/manual/en/iterator.current.php
263
     * @return mixed
264
     */
265
    public function current()
266
    {
267
        return $this->current->value();
268
    }
269
270
    /**
271
     * @link http://php.net/manual/en/iterator.next.php
272
     * @return void
273
     */
274
    public function next()
275
    {
276
        $this->forward();
277
    }
278
279
    /**
280
     * @return void
281
     */
282
    public function prev()
283
    {
284
        $this->backward();
285
    }
286
287
    /**
288
     * @link http://php.net/manual/en/iterator.key.php
289
     * @return mixed
290
     */
291
    public function key()
292
    {
293
        return $this->offset;
294
    }
295
296
    /**
297
     * @link http://php.net/manual/en/iterator.valid.php
298
     * @return boolean
299
     */
300
    public function valid()
301
    {
302
        return $this->current instanceof LinkedDataNode;
303
    }
304
305
    /**
306
     * @link http://php.net/manual/en/iterator.rewind.php
307
     * @return void
308
     */
309
    public function rewind()
310
    {
311
        $this->current = $this->head;
312
        $this->offset = -1;
313
        $this->forward();
314
    }
315
316
    public function search($value)
317
    {
318
        return $this->indexOf($value);
319
    }
320
321
    /**
322
     * @link http://php.net/manual/en/seekableiterator.seek.php
323
     * @param int $position
324
     * @return mixed
325
     * @throws OutOfBoundsException
326
     * @throws Exception
327
     */
328
    public function seek($position)
329
    {
330
        $index = $this->intGuard($position);
331
        $this->indexGuard($index, __METHOD__);
332
333
        if ($index === 0) {
334
            return $this->seekHead()->value();
335
        } elseif ($index === $this->size - 1) {
336
            return $this->seekTail()->value();
337
        }
338
339
        return $this->seekTo($index)->value();
340
    }
341
342
    /**
343
     * @param mixed $i
344
     * @return int
345
     * @throws Exception
346
     */
347
    private function intGuard($i)
348
    {
349
        if (filter_var($i, FILTER_VALIDATE_INT) === false) {
350
            throw new Exception;
351
        }
352
353
        return (int) $i;
354
    }
355
356
357
    /**
358
     * @param $value
359
     * @param callable $callback
360
     * @return int
361
     */
362
    public function indexOf($value, callable $callback = null)
363
    {
364
        $equal = $f ?? function ($a, $b) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $f seems to never exist and therefore isset should always be false.
Loading history...
365
            return $a === $b;
366
        };
367
368
        $filter = $this->filter(function ($item) use ($equal, $value) {
369
            return $equal($item, $value);
370
        });
371
372
        foreach ($filter as $key => $value) {
373
            return $key;
374
        }
375
376
        return -1;
377
    }
378
379
    public function contains(...$values): bool
380
    {
381
        return $this->indexOf($values, $f) >= 0;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $f seems to be never defined.
Loading history...
382
    }
383
384
    /**
385
     * Extract the elements after the first of a list, which must be non-empty.
386
     * @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...
387
     * @throws EmptyException
388
     */
389
    public function tail()
390
    {
391
        $this->emptyGuard(__METHOD__);
392
        return $this->copyFromContext($this->head->next()->next());
393
    }
394
395
    public function __clone()
396
    {
397
        $list = $this->copyFromContext($this->head->next());
398
399
        $this->head = $list->head;
400
        $this->tail = $list->tail;
401
402
        $this->current = $this->head;
403
        $this->offset = -1;
404
405
        $this->size = $list->size;
406
    }
407
408
    private function copyFromContext(LinkedNode $context)
409
    {
410
        $list = new static();
411
412
        for ($n = $context; $n !== $this->tail; $n = $n->next()) {
413
            /**
414
             * @var LinkedDataNode $n
415
             */
416
            $list->push($n->value());
417
        }
418
419
        return $list;
420
    }
421
422
    private function insertBetween(LinkedNode $a, LinkedNode $b, $value)
423
    {
424
        $n = new LinkedDataNode($value);
425
426
        $a->setNext($n);
427
        $b->setPrev($n);
428
429
        $n->setPrev($a);
430
        $n->setNext($b);
431
432
        $this->current = $n;
433
        $this->size++;
434
    }
435
436
    private function forward()
437
    {
438
        $this->current = $this->current->next();
439
        $this->offset++;
440
    }
441
442
    private function backward()
443
    {
444
        $this->current = $this->current->prev();
445
        $this->offset--;
446
    }
447
448
    public function remove(int $index)
449
    {
450
        return $this->removeNode($this->guardedSeek($index, __METHOD__));
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->removeNode($this-...ek($index, __METHOD__)) targeting Mbh\Collection\DoublyLinkedList::removeNode() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
451
    }
452
453
    private function removeNode(LinkedNode $n)
454
    {
455
        $prev = $n->prev();
456
        $next = $n->next();
457
458
        $prev->setNext($next);
459
        $next->setPrev($prev);
460
461
        $this->size--;
462
    }
463
464
    /**
465
     * @return LinkedDataNode
466
     */
467
    private function seekTail()
468
    {
469
        $this->offset = $this->size - 1;
470
        return $this->current = $this->tail->prev();
471
    }
472
473
    /**
474
     * @return LinkedDataNode
475
     */
476
    private function seekHead()
477
    {
478
        $this->offset = 0;
479
        return $this->current = $this->head->next();
480
    }
481
482
    /**
483
     * @param $offset
484
     * @return LinkedDataNode
485
     */
486
    private function seekTo($offset)
487
    {
488
        $n = abs($diff = $this->offset - $offset);
489
        $action = ($diff < 0) ? 'forward' : 'backward';
490
491
        for ($i = 0; $i < $n; $i++) {
492
            $this->$action();
493
        }
494
495
        return $this->current;
496
    }
497
498
    private function indexGuard($offset, $method)
499
    {
500
        if (!$this->offsetExists($offset)) {
501
            throw new OutOfBoundsException(
502
                "{$method} was called with invalid index: {$offset}"
503
            );
504
        }
505
    }
506
507
    private function guardedSeek($index, $method)
508
    {
509
        $index = $this->intGuard($index);
510
        $this->indexGuard($index, $method);
511
512
        return $this->seekTo($index);
513
    }
514
515
    public function toArray(): array
516
    {
517
        $array = [];
518
        $context = $this->head->next();
519
520
        for ($n = $context; $n !== $this->tail; $n = $n->next()) {
521
            /**
522
             * @var LinkedDataNode $n
523
             */
524
            $array[] = $n->value();
525
        }
526
527
        return $array;
528
    }
529
530
    public function unserialize($serialized)
531
    {
532
    }
533
534
    public function clear()
535
    {
536
        $this->init();
537
    }
538
}
539