Passed
Push — 1.x ( 36b118...cb0b9f )
by Ulises Jeremias
02:31
created

LinkedList::insertAfter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
1
<?php namespace Mbh\Collection\Traits\Sequenceable;
2
3
use Mbh\Collection\Interfaces\Collection as CollectionInterface;
4
use Mbh\Collection\Interfaces\Functional as FunctionalInterface;
5
use Mbh\Collection\Interfaces\Sequenceable as SequenceableInterface;
6
use Mbh\Collection\Internal\Interfaces\LinkedNode;
7
use Mbh\Collection\Internal\LinkedDataNode;
8
use Mbh\Collection\Internal\LinkedTerminalNode;
9
use Mbh\Interfaces\Allocated as AllocatedInterface;
10
use Mbh\Traits\Capacity;
11
use Mbh\Traits\EmptyGuard;
12
use SplFixedArray;
13
use Traversable;
14
use OutOfBoundsException;
15
use Exception;
16
17
/**
18
 * MBHFramework
19
 *
20
 * @link      https://github.com/MBHFramework/mbh-framework
21
 * @copyright Copyright (c) 2017 Ulises Jeremias Cornejo Fandos
22
 * @license   https://github.com/MBHFramework/mbh-framework/blob/master/LICENSE (MIT License)
23
 */
24
25
trait LinkedList
26
{
27
    use LinkedList\ArrayAccess;
28
    use LinkedList\Countable;
29
    use LinkedList\Iterator;
30
31
    protected $head;
32
    protected $tail;
33
    protected $size;
34
    protected $current;
35
    protected $offset = -1;
36
37
    /**
38
     * Create an fixed array
39
     *
40
     * @param array|Traversable $array data
41
     */
42
    public function __construct($array = null)
43
    {
44
        $this->head = $head = new LinkedTerminalNode();
45
        $this->tail = $tail = new LinkedTerminalNode();
46
47
        $head->setNext($tail);
48
        $tail->setPrev($head);
49
50
        $this->current = $this->head;
51
52
        if ($array) {
53
            $this->pushAll($array);
54
        }
55
    }
56
57
    public function __clone()
58
    {
59
        $list = $this->copyFromContext($this->head->next());
60
61
        $this->head = $list->head;
62
        $this->tail = $list->tail;
63
64
        $this->current = $this->head;
65
        $this->offset = -1;
66
67
        $this->size = $list->size;
68
    }
69
70
    protected function backward()
71
    {
72
        $this->current = $this->current->prev();
73
        $this->offset--;
74
    }
75
76
    public function clear()
77
    {
78
        $this->head->setNext($this->tail);
79
        $this->tail->setPrev($this->head);
80
81
        $this->current = $this->head;
82
        $this->size = 0;
83
        $this->offset = -1;
84
    }
85
86
    public function contains(...$values): bool
87
    {
88
        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...
89
    }
90
91
    public function copy()
92
    {
93
        return $this->copyFromContext($this->head->next());
94
    }
95
96
    protected function copyFromContext(LinkedNode $context)
97
    {
98
        $list = new static();
99
100
        for ($n = $context; $n !== $this->tail; $n = $n->next()) {
101
            /**
102
             * @var LinkedDataNode $n
103
             */
104
            $list->push($n->value());
105
        }
106
107
        return $list;
108
    }
109
110
    protected function forward()
111
    {
112
        $this->current = $this->current->next();
113
        $this->offset++;
114
    }
115
116
    public function first()
117
    {
118
        $this->emptyGuard(__METHOD__);
0 ignored issues
show
Bug introduced by
It seems like emptyGuard() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

118
        $this->/** @scrutinizer ignore-call */ 
119
               emptyGuard(__METHOD__);
Loading history...
119
        return $this->seekHead()->value();
120
    }
121
122
    public function get(int $index)
123
    {
124
        return $this[$index];
125
    }
126
127
    protected function getSize()
128
    {
129
        return $this->size;
130
    }
131
132
    protected function getValues(): Traversable
133
    {
134
        return SplFixedArray::fromArray($this->toArray());
135
    }
136
137
    protected function guardedSeek($index, $method)
138
    {
139
        $index = $this->intGuard($index);
140
        $this->indexGuard($index, $method);
141
142
        return $this->seekTo($index);
143
    }
144
145
    protected function indexGuard($offset, $method)
146
    {
147
        if (!$this->offsetExists($offset)) {
148
            throw new OutOfBoundsException(
149
                "{$method} was called with invalid index: {$offset}"
150
            );
151
        }
152
    }
153
154
    /**
155
     * @param $value
156
     * @param callable $callback
157
     * @return int
158
     */
159
    public function indexOf($value, callable $callback = null)
160
    {
161
        $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...
162
            return $a === $b;
163
        };
164
165
        $filter = $this->filter(function ($item) use ($equal, $value) {
0 ignored issues
show
Bug introduced by
It seems like filter() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

165
        /** @scrutinizer ignore-call */ 
166
        $filter = $this->filter(function ($item) use ($equal, $value) {
Loading history...
166
            return $equal($item, $value);
167
        });
168
169
        foreach ($filter as $key => $value) {
170
            return $key;
171
        }
172
173
        return -1;
174
    }
175
176
    public function insert(int $index, ...$values)
177
    {
178
        foreach ($values as &$value) {
179
            $this->insertBefore($index++, $value);
180
        }
181
    }
182
183
    /**
184
     * @param int $position
185
     * @param mixed $value
186
     * @return void
187
     *
188
     * @throws OutOfBoundsException
189
     */
190
    public function insertAfter(int $position, $value)
191
    {
192
        $n = $this->guardedSeek($position, __METHOD__);
193
        $this->insertBetween($n, $n->next(), $value);
194
        $this->current = $this->current->prev();
195
    }
196
197
    /**
198
     * @param int $position
199
     * @param mixed $value
200
     * @return void
201
     *
202
     * @throws OutOfBoundsException
203
     */
204
    public function insertBefore(int $position, $value)
205
    {
206
        $n = $this->guardedSeek($position, __METHOD__);
207
        $this->insertBetween($n->prev(), $n, $value);
208
        $this->current = $this->current->next();
209
        $this->offset++;
210
    }
211
212
    protected function insertBetween(LinkedNode $a, LinkedNode $b, $value)
213
    {
214
        $n = new LinkedDataNode($value);
215
216
        $a->setNext($n);
217
        $b->setPrev($n);
218
219
        $n->setPrev($a);
220
        $n->setNext($b);
221
222
        $this->current = $n;
223
        $this->size++;
224
    }
225
226
    /**
227
     * @param mixed $i
228
     * @return int
229
     * @throws Exception
230
     */
231
    protected function intGuard($i)
232
    {
233
        if (filter_var($i, FILTER_VALIDATE_INT) === false) {
234
            throw new Exception;
235
        }
236
237
        return (int) $i;
238
    }
239
240
    public function last()
241
    {
242
        $this->emptyGuard(__METHOD__);
243
        return $this->seekTail()->value();
244
    }
245
246
    public function pop()
247
    {
248
        $this->emptyGuard(__METHOD__);
249
250
        $n = $this->seekTail();
251
        $this->removeNode($n);
252
253
        return $n->value();
254
    }
255
256
    public function push(...$values)
257
    {
258
        $this->pushAll($values);
259
    }
260
261
    protected function pushAll($values)
262
    {
263
        foreach ($values as $key => $value) {
264
            $this->insertBetween($this->tail->prev(), $this->tail, $value);
265
            $this->offset = $this->size - 1;
266
        }
267
    }
268
269
    public function remove(int $index)
270
    {
271
        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\Traits\Se...inkedList::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...
272
    }
273
274
    protected function removeNode(LinkedNode $n)
275
    {
276
        $prev = $n->prev();
277
        $next = $n->next();
278
279
        $prev->setNext($next);
280
        $next->setPrev($prev);
281
282
        $this->size--;
283
    }
284
285
    /**
286
     * @link http://php.net/manual/en/seekableiterator.seek.php
287
     * @param int $position
288
     * @return mixed
289
     * @throws OutOfBoundsException
290
     * @throws Exception
291
     */
292
    public function seek($position)
293
    {
294
        $index = $this->intGuard($position);
295
        $this->indexGuard($index, __METHOD__);
296
297
        if ($index === 0) {
298
            return $this->seekHead()->value();
299
        } elseif ($index === $this->size - 1) {
300
            return $this->seekTail()->value();
301
        }
302
303
        return $this->seekTo($index)->value();
304
    }
305
306
    /**
307
     * @return LinkedDataNode
308
     */
309
    protected function seekTail()
310
    {
311
        $this->offset = $this->size - 1;
312
        return $this->current = $this->tail->prev();
313
    }
314
315
    /**
316
     * @return LinkedDataNode
317
     */
318
    protected function seekHead()
319
    {
320
        $this->offset = 0;
321
        return $this->current = $this->head->next();
322
    }
323
324
    /**
325
     * @param $offset
326
     * @return LinkedDataNode
327
     */
328
    protected function seekTo($offset)
329
    {
330
        $n = abs($diff = $this->offset - $offset);
331
        $action = ($diff < 0) ? 'forward' : 'backward';
332
333
        for ($i = 0; $i < $n; $i++) {
334
            $this->$action();
335
        }
336
337
        return $this->current;
338
    }
339
340
    /**
341
     * @inheritDoc
342
     */
343
    public function set(int $index, $value)
344
    {
345
        $this->offsetSet($index, $value);
346
    }
347
348
    protected function setValues(Traversable $traversable)
349
    {
350
        $this->clear();
351
        $this->pushAll($traversable);
352
    }
353
354
    public function shift()
355
    {
356
        $this->emptyGuard(__METHOD__);
357
358
        $n = $this->seekHead();
359
        $this->removeNode($n);
360
361
        return $n->value();
362
    }
363
364
    /**
365
     * Extract the elements after the first of a list, which must be non-empty.
366
     * @return DoublyLinkedList
0 ignored issues
show
Bug introduced by
The type Mbh\Collection\Traits\Se...ceable\DoublyLinkedList 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...
367
     * @throws EmptyException
368
     */
369
    public function tail()
370
    {
371
        $this->emptyGuard(__METHOD__);
372
        return $this->copyFromContext($this->head->next()->next());
373
    }
374
375
    public function toArray(): array
376
    {
377
        $array = [];
378
        $context = $this->head->next();
379
380
        for ($n = $context; $n !== $this->tail; $n = $n->next()) {
381
            /**
382
             * @var LinkedDataNode $n
383
             */
384
            $array[] = $n->value();
385
        }
386
387
        return $array;
388
    }
389
390
    public function unserialize($serialized)
391
    {
392
    }
393
394
    public function unshift(...$values)
395
    {
396
        foreach ($values as &$value) {
397
            $this->insertBetween($this->head, $this->head->next(), $value);
398
            $this->offset = 0;
399
        }
400
    }
401
402
    /**
403
     * @inheritDoc
404
     */
405
    protected function validIndex(int $index)
406
    {
407
        return $index >= 0 && $index < $this->count();
408
    }
409
410
    abstract public function isEmpty(): bool;
411
}
412