LinkedList   D
last analyzed

Complexity

Total Complexity 58

Size/Duplication

Total Lines 504
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 58
eloc 192
c 0
b 0
f 0
dl 0
loc 504
rs 4.5599

25 Methods

Rating   Name   Duplication   Size   Complexity  
A swap() 0 42 3
A pushBefore() 0 26 4
A getIndexOf() 0 11 3
A toArray() 0 8 2
A setPrevFor() 0 9 2
A merge() 0 11 3
A popBack() 0 3 1
A checkIntegrity() 0 52 5
A clear() 0 6 1
A delete() 0 20 3
A popBackPosition() 0 6 2
A getLast() 0 3 1
A getIterator() 0 3 1
A __clone() 0 15 3
A getPositionsArray() 0 13 3
A popFront() 0 6 2
A pushFront() 0 3 1
A __construct() 0 4 2
A pushBack() 0 3 1
A popFrontPosition() 0 6 2
A setNextFor() 0 9 2
A count() 0 3 1
A getFirst() 0 3 1
A sort() 0 28 5
A pushAfter() 0 26 4

How to fix   Complexity   

Complex Class

Complex classes like LinkedList often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LinkedList, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Smoren\Containers\Structs;
4
5
use Countable;
6
use Exception;
7
use IteratorAggregate;
8
use Smoren\Helpers\LoopHelper;
9
use Smoren\Containers\Exceptions\LinkedListException;
10
11
/**
12
 * Class LinkedList
13
 */
14
class LinkedList implements IteratorAggregate, Countable
15
{
16
    /**
17
     * @var LinkedListItem|null first element of list
18
     */
19
    protected ?LinkedListItem $first = null;
20
    /**
21
     * @var LinkedListItem|null last element of list
22
     */
23
    protected ?LinkedListItem $last = null;
24
    /**
25
     * @var int List size
26
     */
27
    protected int $length = 0;
28
29
    /**
30
     * Create new list by merging several another lists
31
     * @param LinkedList ...$lists
32
     * @return LinkedList
33
     */
34
    public static function merge(LinkedList ...$lists): LinkedList
35
    {
36
        $result = new LinkedList();
37
38
        foreach($lists as $list) {
39
            foreach($list as $value) {
40
                $result->pushBack($value);
41
            }
42
        }
43
44
        return $result;
45
    }
46
47
    /**
48
     * LinkedList constructor.
49
     * @param array $input default list content
50
     */
51
    public function __construct(array $input = [])
52
    {
53
        foreach($input as $item) {
54
            $this->pushBack($item);
55
        }
56
    }
57
58
    /**
59
     * Pushes element to front of list
60
     * @param mixed $data data value of element
61
     * @return LinkedListItem
62
     */
63
    public function pushFront($data): LinkedListItem
64
    {
65
        return $this->pushAfter(null, $data);
66
    }
67
68
    /**
69
     * Pushes element to back of list
70
     * @param mixed $data data value of element
71
     * @return LinkedListItem
72
     */
73
    public function pushBack($data): LinkedListItem
74
    {
75
        return $this->pushBefore(null, $data);
76
    }
77
78
    /**
79
     * Pushes new element to after target element position
80
     * @param LinkedListItem|null $item target element position
81
     * @param mixed $data data value of new element
82
     * @return LinkedListItem
83
     */
84
    public function pushAfter(?LinkedListItem $item, $data): LinkedListItem
85
    {
86
        $newItem = new LinkedListItem($data, null, null);
87
88
        if($item === null) {
89
            if($this->first !== null) {
90
                return $this->pushBefore($this->first, $data);
91
            }
92
            $this->first = $newItem;
93
            $this->last = $newItem;
94
        } else {
95
            $bufNext = $item->getNext();
96
            $item->setNext($newItem);
97
            $newItem->setPrev($item);
98
            $newItem->setNext($bufNext);
99
100
            if($bufNext === null) {
101
                $this->last = $newItem;
102
            } else {
103
                $bufNext->setPrev($newItem);
104
            }
105
        }
106
107
        $this->length++;
108
109
        return $newItem;
110
    }
111
112
    /**
113
     * Pushes new element to before target element position
114
     * @param LinkedListItem|null $item target element position
115
     * @param mixed $data data value of new element
116
     * @return LinkedListItem
117
     */
118
    public function pushBefore(?LinkedListItem $item, $data): LinkedListItem
119
    {
120
        $newItem = new LinkedListItem($data, null, null);
121
122
        if($item === null) {
123
            if($this->last !== null) {
124
                return $this->pushAfter($this->last, $data);
125
            }
126
            $this->first = $newItem;
127
            $this->last = $newItem;
128
        } else {
129
            $bufPrev = $item->getPrev();
130
            $item->setPrev($newItem);
131
            $newItem->setNext($item);
132
            $newItem->setPrev($bufPrev);
133
134
            if($bufPrev === null) {
135
                $this->first = $newItem;
136
            } else {
137
                $bufPrev->setNext($newItem);
138
            }
139
        }
140
141
        $this->length++;
142
143
        return $newItem;
144
    }
145
146
    /**
147
     * Removes element from the front of list
148
     * @return mixed data value of removed element
149
     * @throws LinkedListException
150
     */
151
    public function popFront()
152
    {
153
        if(!$this->length) {
154
            throw new LinkedListException('empty', LinkedListException::STATUS_EMPTY);
155
        }
156
        return $this->popFrontPosition()->getData();
157
    }
158
159
    /**
160
     * Removes element from the back of list
161
     * @return mixed data value of removed element
162
     * @throws LinkedListException
163
     */
164
    public function popBack()
165
    {
166
        return $this->popBackPosition()->getData();
167
    }
168
169
    /**
170
     * Removes element from the front of list
171
     * @return LinkedListItem removed element position
172
     * @throws LinkedListException
173
     */
174
    public function popFrontPosition(): LinkedListItem
175
    {
176
        if(!$this->length) {
177
            throw new LinkedListException('empty', LinkedListException::STATUS_EMPTY);
178
        }
179
        return $this->delete($this->first);
0 ignored issues
show
Bug introduced by
It seems like $this->first can also be of type null; however, parameter $item of Smoren\Containers\Structs\LinkedList::delete() does only seem to accept Smoren\Containers\Structs\LinkedListItem, maybe add an additional type check? ( Ignorable by Annotation )

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

179
        return $this->delete(/** @scrutinizer ignore-type */ $this->first);
Loading history...
180
    }
181
182
    /**
183
     * Removes element from the back of list
184
     * @return LinkedListItem removed element position
185
     * @throws LinkedListException
186
     */
187
    public function popBackPosition(): LinkedListItem
188
    {
189
        if(!$this->length) {
190
            throw new LinkedListException('empty', LinkedListException::STATUS_EMPTY);
191
        }
192
        return $this->delete($this->last);
0 ignored issues
show
Bug introduced by
It seems like $this->last can also be of type null; however, parameter $item of Smoren\Containers\Structs\LinkedList::delete() does only seem to accept Smoren\Containers\Structs\LinkedListItem, maybe add an additional type check? ( Ignorable by Annotation )

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

192
        return $this->delete(/** @scrutinizer ignore-type */ $this->last);
Loading history...
193
    }
194
195
    /**
196
     * Removes element from target element position
197
     * @param LinkedListItem $item target element position
198
     * @return LinkedListItem old position of element
199
     */
200
    public function delete(LinkedListItem $item): LinkedListItem
201
    {
202
        $prev = $item->getPrev();
203
        $next = $item->getNext();
204
205
        if($prev !== null) {
206
            $prev->setNext($next);
207
        } else {
208
            $this->first = $next;
209
        }
210
211
        if($next !== null) {
212
            $next->setPrev($prev);
213
        } else {
214
            $this->last = $prev;
215
        }
216
217
        $this->length--;
218
219
        return $item;
220
    }
221
222
    /**
223
     * Swaps two elements
224
     * @param LinkedListItem $lhs first element position
225
     * @param LinkedListItem $rhs second element position
226
     * @return $this
227
     */
228
    public function swap(LinkedListItem $lhs, LinkedListItem $rhs): self
229
    {
230
        $lhsPrev = $lhs->getPrev();
231
        $lhsNext = $lhs->getNext();
232
        $rhsPrev = $rhs->getPrev();
233
        $rhsNext = $rhs->getNext();
234
235
        if($lhsNext === $rhs) {
236
            $rhs->setNext($lhs);
237
            $lhs->setPrev($rhs);
238
239
            $rhs->setPrev($lhsPrev);
240
            $this->setNextFor($lhsPrev, $rhs);
241
242
            $lhs->setNext($rhsNext);
243
            $this->setPrevFor($rhsNext, $lhs);
244
        } elseif($rhsNext === $lhs) {
245
            $lhs->setNext($rhs);
246
            $rhs->setPrev($lhs);
247
248
            $lhs->setPrev($rhsPrev);
249
            $this->setNextFor($rhsPrev, $lhs);
250
251
            $rhs->setNext($lhsNext);
252
            $this->setPrevFor($lhsNext, $rhs);
253
        } else {
254
            $lhs->setNext($rhsNext);
255
            $this->setPrevFor($rhsNext, $lhs);
256
257
            $lhs->setPrev($rhsPrev);
258
            $this->setNextFor($rhsPrev, $lhs);
259
260
            $rhs->setNext($lhsNext);
261
            $this->setPrevFor($lhsNext, $rhs);
262
263
            $rhs->setPrev($lhsPrev);
264
            $this->setNextFor($lhsPrev, $rhs);
265
        }
266
267
        //$this->checkIntegrity();
268
269
        return $this;
270
    }
271
272
    /**
273
     * Clears list
274
     * @return $this
275
     */
276
    public function clear(): self
277
    {
278
        $this->first = null;
279
        $this->last = null;
280
        $this->length = 0;
281
        return $this;
282
    }
283
284
    /**
285
     * Sorts element via comparator callback
286
     * @param callable $comparator comparator callback
287
     * @return $this
288
     * @throws Exception
289
     */
290
    public function sort(callable $comparator): self
291
    {
292
        if($this->length <= 1) {
293
            return $this;
294
        }
295
296
        do {
297
            $flagStop = true;
298
            $it = $this->getIterator();
299
            $it->rewind();
300
            for($i=0; $i<$this->length-1; $i++) {
301
                $lhs = $it->current();
302
                /** @var LinkedListItem $lhsItem */
303
                $lhsItem = $it->key();
304
305
                $it->next();
306
                $rhs = $it->current();
307
                /** @var LinkedListItem $rhsItem */
308
                $rhsItem = $it->key();
309
310
                if($comparator($lhs, $rhs, $lhsItem, $rhsItem)) {
311
                    $this->swap($lhsItem, $rhsItem);
312
                    $flagStop = false;
313
                }
314
            }
315
        } while(!$flagStop);
316
317
        return $this;
318
    }
319
320
    /**
321
     * Converts list to array
322
     * @return array
323
     */
324
    public function toArray(): array
325
    {
326
        $result = [];
327
        foreach($this as $val) {
328
            $result[] = $val;
329
        }
330
331
        return $result;
332
    }
333
334
    /**
335
     * Returns first position of list
336
     * @return LinkedListItem|null
337
     */
338
    public function getFirst(): ?LinkedListItem
339
    {
340
        return $this->first;
341
    }
342
343
    /**
344
     * Returns last position of list
345
     * @return LinkedListItem|null
346
     */
347
    public function getLast(): ?LinkedListItem
348
    {
349
        return $this->last;
350
    }
351
352
    /**
353
     * Returns list iterator
354
     * @return LinkedListIterator
355
     */
356
    public function getIterator(): LinkedListIterator
357
    {
358
        return new LinkedListIterator($this);
359
    }
360
361
    /**
362
     * Returns list size
363
     * @return int
364
     */
365
    public function count(): int
366
    {
367
        return $this->length;
368
    }
369
370
    /**
371
     * Inserts element before specified position
372
     * @param LinkedListItem|null $positionFor position to insert before
373
     * @param LinkedListItem $element element to insert
374
     * @return $this
375
     */
376
    protected function setPrevFor(?LinkedListItem $positionFor, LinkedListItem $element): self
377
    {
378
        if($positionFor !== null) {
379
            $positionFor->setPrev($element);
380
        } else {
381
            $this->last = $element;
382
        }
383
384
        return $this;
385
    }
386
387
    /**
388
     * Inserts element after specified position
389
     * @param LinkedListItem|null $positionFor position to insert after
390
     * @param LinkedListItem $element element to insert
391
     * @return $this
392
     */
393
    protected function setNextFor(?LinkedListItem $positionFor, LinkedListItem $element): self
394
    {
395
        if($positionFor !== null) {
396
            $positionFor->setNext($element);
397
        } else {
398
            $this->first = $element;
399
        }
400
401
        return $this;
402
    }
403
404
    /**
405
     * Returns positions array
406
     * @return LinkedListItem[]
407
     */
408
    public function getPositionsArray(?callable $mapper = null): array
409
    {
410
        $result = [];
411
412
        foreach($this as $pos => $val) {
413
            if(is_callable($mapper)) {
414
                $result[$mapper($pos)] = $pos;
415
            } else {
416
                $result[] = $pos;
417
            }
418
        }
419
420
        return $result;
421
    }
422
423
    /**
424
     * Returns index of position in list
425
     * @param LinkedListItem $position position to get index
426
     * @return int
427
     */
428
    public function getIndexOf(LinkedListItem $position): int
429
    {
430
        $index = 0;
431
        foreach($this as $pos => $val) {
432
            if($position === $pos) {
433
                break;
434
            }
435
            ++$index;
436
        }
437
438
        return $index;
439
    }
440
441
    /**
442
     * Debug method to check integrity of list structure
443
     * @return $this
444
     * @throws LinkedListException
445
     */
446
    public function checkIntegrity(): self
447
    {
448
        if($this->first->getPrev() !== null) {
0 ignored issues
show
Bug introduced by
The method getPrev() does not exist on null. ( Ignorable by Annotation )

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

448
        if($this->first->/** @scrutinizer ignore-call */ getPrev() !== null) {

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...
449
            throw new LinkedListException(
450
                'integrity violation',
451
                LinkedListException::STATUS_INTEGRITY_VIOLATION,
452
                null,
453
                [
454
                    'reason' => 'first_hav_prev',
455
                    'index' => 0,
456
                    'position' => $this->first,
457
                    'value' => $this->first->getData(),
458
                ]
459
            );
460
        }
461
462
        if($this->last->getNext() !== null) {
0 ignored issues
show
Bug introduced by
The method getNext() does not exist on null. ( Ignorable by Annotation )

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

462
        if($this->last->/** @scrutinizer ignore-call */ getNext() !== null) {

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...
463
            throw new LinkedListException(
464
                'integrity violation',
465
                LinkedListException::STATUS_INTEGRITY_VIOLATION,
466
                null,
467
                [
468
                    'reason' => 'last_get_next',
469
                    'index' => $this->count()-1,
470
                    'position' => $this->last,
471
                    'value' => $this->last->getData(),
472
                ]
473
            );
474
        }
475
476
        $objMap = [];
477
        $index = 0;
478
        foreach($this as $pos => $val) {
479
            $objId = spl_object_id($pos);
480
            if(isset($objMap[$objId])) {
481
                throw new LinkedListException(
482
                    'integrity violation',
483
                    LinkedListException::STATUS_INTEGRITY_VIOLATION,
484
                    null,
485
                    [
486
                        'reason' => 'duplicate',
487
                        'index' => $index,
488
                        'position' => $pos,
489
                        'value' => $val,
490
                    ]
491
                );
492
            }
493
            ++$index;
494
            $objMap[$objId] = $pos;
495
        }
496
497
        return $this;
498
    }
499
500
    /**
501
     * Clones object
502
     */
503
    public function __clone()
504
    {
505
        $buf = [];
506
        foreach($this as $pos => $val) {
507
            $buf[] = clone $pos;
508
        }
509
510
        LoopHelper::eachPair($buf, function(LinkedListItem $lhs, LinkedListItem $rhs) {
511
            $lhs->setNext($rhs);
512
            $rhs->setPrev($lhs);
513
        });
514
515
        if(count($buf)) {
516
            $this->first = $buf[0];
517
            $this->last = $buf[count($buf)-1];
518
        }
519
    }
520
}
521