Passed
Push — 1.x ( 650a57...b30111 )
by Ulises Jeremias
02:24
created

DoublyLinkedList::setValues()   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 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\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->head = $head = new LinkedTerminalNode();
56
        $this->tail = $tail = new LinkedTerminalNode();
57
58
        $head->setNext($tail);
59
        $tail->setPrev($head);
60
61
        $this->current = $this->head;
62
63
        if ($array) {
64
            $this->pushAll($array);
65
        }
66
    }
67
68
    public function copy()
69
    {
70
        return $this->copyFromContext($this->head->next());
71
    }
72
73
    public function isEmpty(): bool
74
    {
75
        return $this->size === 0;
76
    }
77
78
    private function pushAll($values)
79
    {
80
        foreach ($values as $key => $value) {
81
            $this->insertBetween($this->tail->prev(), $this->tail, $value);
82
            $this->offset = $this->size - 1;
83
        }
84
    }
85
86
    public function push(...$values)
87
    {
88
        $this->pushAll($values);
89
    }
90
91
    public function unshift(...$values)
92
    {
93
        foreach ($values as &$value) {
94
            $this->insertBetween($this->head, $this->head->next(), $value);
95
            $this->offset = 0;
96
        }
97
    }
98
99
    public function pop()
100
    {
101
        $this->emptyGuard(__METHOD__);
102
103
        $n = $this->seekTail();
104
        $this->removeNode($n);
105
106
        return $n->value();
107
    }
108
109
    public function shift()
110
    {
111
        $this->emptyGuard(__METHOD__);
112
113
        $n = $this->seekHead();
114
        $this->removeNode($n);
115
116
        return $n->value();
117
    }
118
119
    public function first()
120
    {
121
        $this->emptyGuard(__METHOD__);
122
        return $this->seekHead()->value();
123
    }
124
125
    public function last()
126
    {
127
        $this->emptyGuard(__METHOD__);
128
        return $this->seekTail()->value();
129
    }
130
131
    /**
132
     * @link http://php.net/manual/en/countable.count.php
133
     * @return int
134
     */
135
    public function count(): int
136
    {
137
        return $this->size;
138
    }
139
140
    public function get(int $index)
141
    {
142
        return $this[$index];
143
    }
144
145
    /**
146
     * @inheritDoc
147
     */
148
    public function set(int $index, $value)
149
    {
150
        $this->offsetSet($index, $value);
151
    }
152
153
    /**
154
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
155
     * @param int $offset
156
     * @return boolean
157
     */
158
    public function offsetExists($offset)
159
    {
160
        $index = $this->intGuard($offset);
161
        return $index >= 0 && $index < $this->count();
162
    }
163
164
    /**
165
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
166
     * @param int $offset
167
     * @return mixed
168
     * @throws OutOfBoundsException
169
     */
170
    public function offsetGet($offset)
171
    {
172
        $n = $this->guardedSeek($offset, __METHOD__);
173
        return $n->value();
174
    }
175
176
    /**
177
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
178
     * @param int|null $offset
179
     * @param mixed $value
180
     * @return void
181
     * @throws OutOfBoundsException
182
     */
183
    public function offsetSet($offset, $value)
184
    {
185
        if ($offset === null) {
186
            $this->push($value);
187
            return;
188
        }
189
        $n = $this->guardedSeek($offset, __METHOD__);
190
        $n->setValue($value);
191
    }
192
193
    /**
194
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
195
     * @param int $offset
196
     * @return void
197
     */
198
    public function offsetUnset($offset)
199
    {
200
        $index = $this->intGuard($offset);
201
        if ($this->offsetExists($index)) {
202
            $n = $this->seekTo($index);
203
            $this->removeNode($n);
204
            $this->current = $n->prev();
205
            $this->offset--;
206
        }
207
    }
208
209
    public function insert(int $index, ...$values)
210
    {
211
        foreach ($values as &$value) {
212
            $this->insertBefore($index++, $value);
213
        }
214
    }
215
216
    protected function getValues(): Traversable
217
    {
218
        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...
219
    }
220
221
    protected function setValues(Traversable $traversable)
222
    {
223
        $this->clear();
224
        $this->pushAll($traversable);
225
    }
226
227
    /**
228
     * @param int $position
229
     * @param mixed $value
230
     * @return void
231
     *
232
     * @throws OutOfBoundsException
233
     */
234
    public function insertBefore(int $position, $value)
235
    {
236
        $n = $this->guardedSeek($position, __METHOD__);
237
        $this->insertBetween($n->prev(), $n, $value);
238
        $this->current = $this->current->next();
239
        $this->offset++;
240
    }
241
242
    /**
243
     * @param int $position
244
     * @param mixed $value
245
     * @return void
246
     *
247
     * @throws OutOfBoundsException
248
     */
249
    public function insertAfter(int $position, $value)
250
    {
251
        $n = $this->guardedSeek($position, __METHOD__);
252
        $this->insertBetween($n, $n->next(), $value);
253
        $this->current = $this->current->prev();
254
    }
255
256
    /**
257
     * @link http://php.net/manual/en/iterator.current.php
258
     * @return mixed
259
     */
260
    public function current()
261
    {
262
        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

262
        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...
263
    }
264
265
    /**
266
     * @link http://php.net/manual/en/iterator.next.php
267
     * @return void
268
     */
269
    public function next()
270
    {
271
        $this->forward();
272
    }
273
274
    /**
275
     * @return void
276
     */
277
    public function prev()
278
    {
279
        $this->backward();
280
    }
281
282
    /**
283
     * @link http://php.net/manual/en/iterator.key.php
284
     * @return mixed
285
     */
286
    public function key()
287
    {
288
        return $this->offset;
289
    }
290
291
    /**
292
     * @link http://php.net/manual/en/iterator.valid.php
293
     * @return boolean
294
     */
295
    public function valid()
296
    {
297
        return $this->current instanceof LinkedDataNode;
298
    }
299
300
    /**
301
     * @link http://php.net/manual/en/iterator.rewind.php
302
     * @return void
303
     */
304
    public function rewind()
305
    {
306
        $this->current = $this->head;
307
        $this->offset = -1;
308
        $this->forward();
309
    }
310
311
    public function search($value)
312
    {
313
        return $this->indexOf($value);
314
    }
315
316
    /**
317
     * @link http://php.net/manual/en/seekableiterator.seek.php
318
     * @param int $position
319
     * @return mixed
320
     * @throws OutOfBoundsException
321
     * @throws Exception
322
     */
323
    public function seek($position)
324
    {
325
        $index = $this->intGuard($position);
326
        $this->indexGuard($index, __METHOD__);
327
328
        if ($index === 0) {
329
            return $this->seekHead()->value();
330
        } elseif ($index === $this->size - 1) {
331
            return $this->seekTail()->value();
332
        }
333
334
        return $this->seekTo($index)->value();
335
    }
336
337
    /**
338
     * @param mixed $i
339
     * @return int
340
     * @throws Exception
341
     */
342
    private function intGuard($i)
343
    {
344
        if (filter_var($i, FILTER_VALIDATE_INT) === false) {
345
            throw new Exception;
346
        }
347
348
        return (int) $i;
349
    }
350
351
352
    /**
353
     * @param $value
354
     * @param callable $callback
355
     * @return int
356
     */
357
    public function indexOf($value, callable $callback = null)
358
    {
359
        $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...
360
            return $a === $b;
361
        };
362
363
        $filter = $this->filter(function ($item) use ($equal, $value) {
364
            return $equal($item, $value);
365
        });
366
367
        foreach ($filter as $key => $value) {
368
            return $key;
369
        }
370
371
        return -1;
372
    }
373
374
    public function contains(...$values): bool
375
    {
376
        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...
377
    }
378
379
    /**
380
     * Extract the elements after the first of a list, which must be non-empty.
381
     * @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...
382
     * @throws EmptyException
383
     */
384
    public function tail()
385
    {
386
        $this->emptyGuard(__METHOD__);
387
        return $this->copyFromContext($this->head->next()->next());
388
    }
389
390
    public function __clone()
391
    {
392
        $list = $this->copyFromContext($this->head->next());
393
394
        $this->head = $list->head;
395
        $this->tail = $list->tail;
396
397
        $this->current = $this->head;
398
        $this->offset = -1;
399
400
        $this->size = $list->size;
401
    }
402
403
    private function copyFromContext(LinkedNode $context)
404
    {
405
        $list = new static();
406
407
        for ($n = $context; $n !== $this->tail; $n = $n->next()) {
408
            /**
409
             * @var LinkedDataNode $n
410
             */
411
            $list->push($n->value());
412
        }
413
414
        return $list;
415
    }
416
417
    private function insertBetween(LinkedNode $a, LinkedNode $b, $value)
418
    {
419
        $n = new LinkedDataNode($value);
420
421
        $a->setNext($n);
422
        $b->setPrev($n);
423
424
        $n->setPrev($a);
425
        $n->setNext($b);
426
427
        $this->current = $n;
428
        $this->size++;
429
    }
430
431
    private function forward()
432
    {
433
        $this->current = $this->current->next();
434
        $this->offset++;
435
    }
436
437
    private function backward()
438
    {
439
        $this->current = $this->current->prev();
440
        $this->offset--;
441
    }
442
443
    public function remove(int $index)
444
    {
445
        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...
446
    }
447
448
    private function removeNode(LinkedNode $n)
449
    {
450
        $prev = $n->prev();
451
        $next = $n->next();
452
453
        $prev->setNext($next);
454
        $next->setPrev($prev);
455
456
        $this->size--;
457
    }
458
459
    /**
460
     * @return LinkedDataNode
461
     */
462
    private function seekTail()
463
    {
464
        $this->offset = $this->size - 1;
465
        return $this->current = $this->tail->prev();
466
    }
467
468
    /**
469
     * @return LinkedDataNode
470
     */
471
    private function seekHead()
472
    {
473
        $this->offset = 0;
474
        return $this->current = $this->head->next();
475
    }
476
477
    /**
478
     * @param $offset
479
     * @return LinkedDataNode
480
     */
481
    private function seekTo($offset)
482
    {
483
        $n = abs($diff = $this->offset - $offset);
484
        $action = ($diff < 0) ? 'forward' : 'backward';
485
486
        for ($i = 0; $i < $n; $i++) {
487
            $this->$action();
488
        }
489
490
        return $this->current;
491
    }
492
493
    private function indexGuard($offset, $method)
494
    {
495
        if (!$this->offsetExists($offset)) {
496
            throw new OutOfBoundsException(
497
                "{$method} was called with invalid index: {$offset}"
498
            );
499
        }
500
    }
501
502
    private function guardedSeek($index, $method)
503
    {
504
        $index = $this->intGuard($index);
505
        $this->indexGuard($index, $method);
506
507
        return $this->seekTo($index);
508
    }
509
510
    public function toArray(): array
511
    {
512
        $array = [];
513
        $context = $this->head->next();
514
515
        for ($n = $context; $n !== $this->tail; $n = $n->next()) {
516
            /**
517
             * @var LinkedDataNode $n
518
             */
519
            $array[] = $n->value();
520
        }
521
522
        return $array;
523
    }
524
525
    public function unserialize($serialized)
526
    {
527
    }
528
529
    public function clear()
530
    {
531
    }
532
}
533