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

DoublyLinkedList::copy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 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->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
    /**
217
     * @param int $position
218
     * @param mixed $value
219
     * @return void
220
     *
221
     * @throws OutOfBoundsException
222
     */
223
    public function insertBefore(int $position, $value)
224
    {
225
        $n = $this->guardedSeek($position, __METHOD__);
226
        $this->insertBetween($n->prev(), $n, $value);
227
        $this->current = $this->current->next();
228
        $this->offset++;
229
    }
230
231
    /**
232
     * @param int $position
233
     * @param mixed $value
234
     * @return void
235
     *
236
     * @throws OutOfBoundsException
237
     */
238
    public function insertAfter(int $position, $value)
239
    {
240
        $n = $this->guardedSeek($position, __METHOD__);
241
        $this->insertBetween($n, $n->next(), $value);
242
        $this->current = $this->current->prev();
243
    }
244
245
    /**
246
     * @link http://php.net/manual/en/iterator.current.php
247
     * @return mixed
248
     */
249
    public function current()
250
    {
251
        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

251
        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...
252
    }
253
254
    /**
255
     * @link http://php.net/manual/en/iterator.next.php
256
     * @return void
257
     */
258
    public function next()
259
    {
260
        $this->forward();
261
    }
262
263
    /**
264
     * @return void
265
     */
266
    public function prev()
267
    {
268
        $this->backward();
269
    }
270
271
    /**
272
     * @link http://php.net/manual/en/iterator.key.php
273
     * @return mixed
274
     */
275
    public function key()
276
    {
277
        return $this->offset;
278
    }
279
280
    /**
281
     * @link http://php.net/manual/en/iterator.valid.php
282
     * @return boolean
283
     */
284
    public function valid()
285
    {
286
        return $this->current instanceof LinkedDataNode;
287
    }
288
289
    /**
290
     * @link http://php.net/manual/en/iterator.rewind.php
291
     * @return void
292
     */
293
    public function rewind()
294
    {
295
        $this->current = $this->head;
296
        $this->offset = -1;
297
        $this->forward();
298
    }
299
300
    public function search($value)
301
    {
302
        return $this->indexOf($value);
303
    }
304
305
    /**
306
     * @link http://php.net/manual/en/seekableiterator.seek.php
307
     * @param int $position
308
     * @return mixed
309
     * @throws OutOfBoundsException
310
     * @throws Exception
311
     */
312
    public function seek($position)
313
    {
314
        $index = $this->intGuard($position);
315
        $this->indexGuard($index, __METHOD__);
316
317
        if ($index === 0) {
318
            return $this->seekHead()->value();
319
        } elseif ($index === $this->size - 1) {
320
            return $this->seekTail()->value();
321
        }
322
323
        return $this->seekTo($index)->value();
324
    }
325
326
    /**
327
     * @param mixed $i
328
     * @return int
329
     * @throws Exception
330
     */
331
    private function intGuard($i)
332
    {
333
        if (filter_var($i, FILTER_VALIDATE_INT) === false) {
334
            throw new Exception;
335
        }
336
337
        return (int) $i;
338
    }
339
340
341
    /**
342
     * @param $value
343
     * @param callable $callback
344
     * @return int
345
     */
346
    public function indexOf($value, callable $callback = null)
347
    {
348
        $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...
349
            return $a === $b;
350
        };
351
352
        $filter = $this->filter(function ($item) use ($equal, $value) {
0 ignored issues
show
Bug introduced by
The method filter() does not exist on Mbh\Collection\DoublyLinkedList. ( Ignorable by Annotation )

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

352
        /** @scrutinizer ignore-call */ 
353
        $filter = $this->filter(function ($item) use ($equal, $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...
353
            return $equal($item, $value);
354
        });
355
356
        foreach ($filter as $key => $value) {
357
            return $key;
358
        }
359
360
        return -1;
361
    }
362
363
    public function contains(...$values): bool
364
    {
365
        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...
366
    }
367
368
    /**
369
     * Extract the elements after the first of a list, which must be non-empty.
370
     * @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...
371
     * @throws EmptyException
372
     */
373
    public function tail()
374
    {
375
        $this->emptyGuard(__METHOD__);
376
        return $this->copyFromContext($this->head->next()->next());
377
    }
378
379
    public function __clone()
380
    {
381
        $list = $this->copyFromContext($this->head->next());
382
383
        $this->head = $list->head;
384
        $this->tail = $list->tail;
385
386
        $this->current = $this->head;
387
        $this->offset = -1;
388
389
        $this->size = $list->size;
390
    }
391
392
    private function copyFromContext(LinkedNode $context)
393
    {
394
        $list = new static();
395
396
        for ($n = $context; $n !== $this->tail; $n = $n->next()) {
397
            /**
398
             * @var LinkedDataNode $n
399
             */
400
            $list->push($n->value());
401
        }
402
403
        return $list;
404
    }
405
406
    private function insertBetween(LinkedNode $a, LinkedNode $b, $value)
407
    {
408
        $n = new LinkedDataNode($value);
409
410
        $a->setNext($n);
411
        $b->setPrev($n);
412
413
        $n->setPrev($a);
414
        $n->setNext($b);
415
416
        $this->current = $n;
417
        $this->size++;
418
    }
419
420
    private function forward()
421
    {
422
        $this->current = $this->current->next();
423
        $this->offset++;
424
    }
425
426
    private function backward()
427
    {
428
        $this->current = $this->current->prev();
429
        $this->offset--;
430
    }
431
432
    public function remove(int $index)
433
    {
434
        $this->indexOf($index);
435
        return $this->removeNode($this[$index]);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->removeNode($this[$index]) 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...
436
    }
437
438
    private function removeNode(LinkedNode $n)
439
    {
440
        $prev = $n->prev();
441
        $next = $n->next();
442
443
        $prev->setNext($next);
444
        $next->setPrev($prev);
445
446
        $this->size--;
447
    }
448
449
    /**
450
     * @return LinkedDataNode
451
     */
452
    private function seekTail()
453
    {
454
        $this->offset = $this->size - 1;
455
        return $this->current = $this->tail->prev();
456
    }
457
458
    /**
459
     * @return LinkedDataNode
460
     */
461
    private function seekHead()
462
    {
463
        $this->offset = 0;
464
        return $this->current = $this->head->next();
465
    }
466
467
    /**
468
     * @param $offset
469
     * @return LinkedDataNode
470
     */
471
    private function seekTo($offset)
472
    {
473
        $n = abs($diff = $this->offset - $offset);
474
        $action = ($diff < 0) ? 'forward' : 'backward';
475
476
        for ($i = 0; $i < $n; $i++) {
477
            $this->$action();
478
        }
479
480
        return $this->current;
481
    }
482
483
    private function indexGuard($offset, $method)
484
    {
485
        if (!$this->offsetExists($offset)) {
486
            throw new OutOfBoundsException(
487
                "{$method} was called with invalid index: {$offset}"
488
            );
489
        }
490
    }
491
492
    private function guardedSeek($index, $method)
493
    {
494
        $index = $this->intGuard($index);
495
        $this->indexGuard($index, $method);
496
497
        return $this->seekTo($index);
498
    }
499
500
    public function toArray(): array
501
    {
502
        $array = [];
503
        $context = $this->head->next();
504
505
        for ($n = $context; $n !== $this->tail; $n = $n->next()) {
506
            /**
507
             * @var LinkedDataNode $n
508
             */
509
            $array[] = $n->value();
510
        }
511
512
        return $array;
513
    }
514
515
    public function unserialize($serialized)
516
    {
517
    }
518
519
    public function clear()
520
    {
521
    }
522
}
523