Passed
Push — 1.x ( f1b002...306f92 )
by Ulises Jeremias
02:31
created

LinkedList::validIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
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 = [])
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
        $this->pushAll($array);
53
    }
54
55
    public function __clone()
56
    {
57
        $list = $this->copyFromContext($this->head->next());
58
59
        $this->head = $list->head;
60
        $this->tail = $list->tail;
61
62
        $this->current = $this->head;
63
        $this->offset = -1;
64
65
        $this->size = $list->size;
66
    }
67
68
    protected function backward()
69
    {
70
        $this->current = $this->current->prev();
71
        $this->offset--;
72
    }
73
74
    public function clear()
75
    {
76
        $this->head->setNext($this->tail);
77
        $this->tail->setPrev($this->head);
78
79
        $this->current = $this->head;
80
        $this->size = 0;
81
        $this->offset = -1;
82
    }
83
84
    protected function copyFromContext(LinkedNode $context)
85
    {
86
        $list = new static();
87
88
        for ($n = $context; $n !== $this->tail; $n = $n->next()) {
89
            /**
90
             * @var LinkedDataNode $n
91
             */
92
            $list->push($n->value());
93
        }
94
95
        return $list;
96
    }
97
98
    protected function forward()
99
    {
100
        $this->current = $this->current->next();
101
        $this->offset++;
102
    }
103
104
    public function first()
105
    {
106
        $this->emptyGuard(__METHOD__);
107
        return $this->seekHead()->value();
108
    }
109
110
    public function get(int $index)
111
    {
112
        return $this[$index];
113
    }
114
115
    protected function getSize()
116
    {
117
        return $this->size;
118
    }
119
120
    protected function getValues(): Traversable
121
    {
122
        return SplFixedArray::fromArray($this->toArray());
123
    }
124
125
    protected function guardedSeek($index, $method)
126
    {
127
        $index = $this->intGuard($index);
128
        $this->indexGuard($index, $method);
129
130
        return $this->seekTo($index);
131
    }
132
133
    protected function indexGuard($offset, $method)
134
    {
135
        if (!$this->offsetExists($offset)) {
136
            throw new OutOfBoundsException(
137
                "{$method} was called with invalid index: {$offset}"
138
            );
139
        }
140
    }
141
142
    /**
143
     * @param $value
144
     * @return int
145
     */
146
    public function indexOf($value)
147
    {
148
        if (($index = $this->search($value)) === null) {
149
            return -1;
150
        }
151
152
        return $index;
153
    }
154
155
    public function insert(int $index, ...$values)
156
    {
157
        foreach ($values as &$value) {
158
            $this->insertBefore($index++, $value);
159
        }
160
    }
161
162
    /**
163
     * @param int $position
164
     * @param mixed $value
165
     * @return void
166
     *
167
     * @throws OutOfBoundsException
168
     */
169
    public function insertAfter(int $position, $value)
170
    {
171
        $n = $this->guardedSeek($position, __METHOD__);
172
        $this->insertBetween($n, $n->next(), $value);
173
        $this->current = $this->current->prev();
174
    }
175
176
    /**
177
     * @param int $position
178
     * @param mixed $value
179
     * @return void
180
     *
181
     * @throws OutOfBoundsException
182
     */
183
    public function insertBefore(int $position, $value)
184
    {
185
        $n = $this->guardedSeek($position, __METHOD__);
186
        $this->insertBetween($n->prev(), $n, $value);
187
        $this->current = $this->current->next();
188
        $this->offset++;
189
    }
190
191
    protected function insertBetween(LinkedNode $a, LinkedNode $b, $value)
192
    {
193
        $n = new LinkedDataNode($value);
194
195
        $a->setNext($n);
196
        $b->setPrev($n);
197
198
        $n->setPrev($a);
199
        $n->setNext($b);
200
201
        $this->current = $n;
202
        $this->size++;
203
    }
204
205
    /**
206
     * @param mixed $i
207
     * @return int
208
     * @throws Exception
209
     */
210
    protected function intGuard($i)
211
    {
212
        if (filter_var($i, FILTER_VALIDATE_INT) === false) {
213
            throw new Exception;
214
        }
215
216
        return (int) $i;
217
    }
218
219
    public function last()
220
    {
221
        $this->emptyGuard(__METHOD__);
222
        return $this->seekTail()->value();
223
    }
224
225
    public function pop()
226
    {
227
        $this->emptyGuard(__METHOD__);
228
229
        $n = $this->seekTail();
230
        $this->removeNode($n);
231
232
        return $n->value();
233
    }
234
235
    public function push(...$values)
236
    {
237
        $this->pushAll($values);
238
    }
239
240
    protected function pushAll($values)
241
    {
242
        foreach ($values as $key => $value) {
243
            $this->insertBetween($this->tail->prev(), $this->tail, $value);
244
            $this->offset = $this->size - 1;
245
        }
246
    }
247
248
    public function remove(int $index)
249
    {
250
        $this->removeNode($this->guardedSeek($index, __METHOD__));
251
    }
252
253
    protected function removeNode(LinkedNode $n)
254
    {
255
        $prev = $n->prev();
256
        $next = $n->next();
257
258
        $prev->setNext($next);
259
        $next->setPrev($prev);
260
261
        $this->size--;
262
    }
263
264
    /**
265
     * @link http://php.net/manual/en/seekableiterator.seek.php
266
     * @param int $position
267
     * @return mixed
268
     * @throws OutOfBoundsException
269
     * @throws Exception
270
     */
271
    public function seek($position)
272
    {
273
        $index = $this->intGuard($position);
274
        $this->indexGuard($index, __METHOD__);
275
276
        if ($index === 0) {
277
            return $this->seekHead()->value();
278
        } elseif ($index === $this->size - 1) {
279
            return $this->seekTail()->value();
280
        }
281
282
        return $this->seekTo($index)->value();
283
    }
284
285
    /**
286
     * @return LinkedDataNode
287
     */
288
    protected function seekTail()
289
    {
290
        $this->offset = $this->size - 1;
291
        return $this->current = $this->tail->prev();
292
    }
293
294
    /**
295
     * @return LinkedDataNode
296
     */
297
    protected function seekHead()
298
    {
299
        $this->offset = 0;
300
        return $this->current = $this->head->next();
301
    }
302
303
    /**
304
     * @param $offset
305
     * @return LinkedDataNode
306
     */
307
    protected function seekTo($offset)
308
    {
309
        $n = abs($diff = $this->offset - $offset);
310
        $action = ($diff < 0) ? 'forward' : 'backward';
311
312
        for ($i = 0; $i < $n; $i++) {
313
            $this->$action();
314
        }
315
316
        return $this->current;
317
    }
318
319
    /**
320
     * @inheritDoc
321
     */
322
    public function set(int $index, $value)
323
    {
324
        $this->offsetSet($index, $value);
325
    }
326
327
    protected function setValues(Traversable $traversable)
328
    {
329
        $this->clear();
330
        $this->pushAll($traversable);
331
    }
332
333
    public function shift()
334
    {
335
        $this->emptyGuard(__METHOD__);
336
337
        $n = $this->seekHead();
338
        $this->removeNode($n);
339
340
        return $n->value();
341
    }
342
343
    /**
344
     * Extract the elements after the first of a list, which must be non-empty.
345
     * @return SequenceableInterface
346
     * @throws EmptyException
347
     */
348
    public function tail()
349
    {
350
        $this->emptyGuard(__METHOD__);
351
        return $this->copyFromContext($this->head->next()->next());
352
    }
353
354
    public function toArray(): array
355
    {
356
        $array = [];
357
        $context = $this->head->next();
358
359
        for ($n = $context; $n !== $this->tail; $n = $n->next()) {
360
            /**
361
             * @var LinkedDataNode $n
362
             */
363
            $array[] = $n->value();
364
        }
365
366
        return $array;
367
    }
368
369
    public function unshift(...$values)
370
    {
371
        foreach ($values as &$value) {
372
            $this->insertBetween($this->head, $this->head->next(), $value);
373
            $this->offset = 0;
374
        }
375
    }
376
377
    /**
378
     * @inheritDoc
379
     */
380
    protected function validIndex(int $index)
381
    {
382
        return $this->offsetExists($index);
383
    }
384
385
    abstract protected function emptyGuard($method);
386
387
    abstract public function isEmpty(): bool;
388
389
    abstract public function search($value);
390
}
391