1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* DataStructures for PHP |
4
|
|
|
* |
5
|
|
|
* @link https://github.com/SiroDiaz/DataStructures |
6
|
|
|
* @copyright Copyright (c) 2017 Siro Díaz Palazón |
7
|
|
|
* @license https://github.com/SiroDiaz/DataStructures/blob/master/README.md (MIT License) |
8
|
|
|
*/ |
9
|
|
|
namespace DataStructures\Lists; |
10
|
|
|
|
11
|
|
|
use DataStructures\Lists\Traits\{CountTrait, ArrayAccessTrait}; |
12
|
|
|
use DataStructures\Lists\Nodes\SimpleLinkedListNode; |
13
|
|
|
use DataStructures\Lists\Interfaces\ListInterface; |
14
|
|
|
use OutOfBoundsException; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* CircularLinkedList |
18
|
|
|
* |
19
|
|
|
* CircularLinkedList is a single and circular linked list that has |
20
|
|
|
* a pointer to the next node and also and last node points to head. |
21
|
|
|
* |
22
|
|
|
* @author Siro Diaz Palazon <[email protected]> |
23
|
|
|
*/ |
24
|
|
|
class CircularLinkedList implements ListInterface { |
25
|
|
|
use ArrayAccessTrait; |
26
|
|
|
private $head; |
27
|
|
|
private $tail; |
28
|
|
|
private $size; |
29
|
|
|
private $current; |
30
|
|
|
private $position; |
31
|
|
|
|
32
|
|
View Code Duplication |
public function __construct() { |
|
|
|
|
33
|
|
|
$this->head = null; |
34
|
|
|
$this->tail = &$this->head; |
35
|
|
|
$this->size = 0; |
36
|
|
|
$this->position = 0; |
37
|
|
|
$this->current = &$this->head; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Inserts data in the specified position. |
42
|
|
|
* |
43
|
|
|
* @param integer $index the position. |
44
|
|
|
* @param mixed $data the data to store. |
45
|
|
|
*/ |
46
|
|
View Code Duplication |
public function insert($index, $data) { |
|
|
|
|
47
|
|
|
if($index < 0) { |
48
|
|
|
throw new OutOfBoundsException(); |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
if($index === 0) { |
52
|
|
|
$this->insertBeginning($data); |
53
|
|
|
} else if($index >= $this->size) { |
54
|
|
|
$this->insertEnd($data); |
55
|
|
|
} else if($index > 0 && $index < $this->size) { |
56
|
|
|
$this->insertAt($index, $data); |
57
|
|
|
} |
58
|
|
|
$this->current = &$this->head; |
59
|
|
|
$this->size++; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Inserts at the beginning of the list. |
64
|
|
|
* |
65
|
|
|
* @param mixed $data |
66
|
|
|
*/ |
67
|
|
|
private function insertBeginning($data) { |
68
|
|
|
$newNode = new SimpleLinkedListNode($data); |
69
|
|
|
if($this->head === null) { |
70
|
|
|
$newNode->next = &$this->head; |
71
|
|
|
$this->head = &$newNode; |
72
|
|
|
$this->tail = &$newNode; |
73
|
|
|
} else { |
74
|
|
|
$this->tail->next = &$newNode; |
75
|
|
|
$newNode->next = $this->head; |
76
|
|
|
$this->head = &$newNode; |
77
|
|
|
} |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Add a new node in the specified index. |
82
|
|
|
* |
83
|
|
|
* @param integer $index the position. |
|
|
|
|
84
|
|
|
* @param mixed $data the data to be stored. |
85
|
|
|
*/ |
86
|
|
|
private function insertEnd($data) { |
87
|
|
|
$newNode = new SimpleLinkedListNode($data); |
88
|
|
|
$this->tail->next = &$newNode; |
89
|
|
|
$newNode->next = &$this->head; |
90
|
|
|
$this->tail = &$newNode; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Add a new node in the specified index. |
95
|
|
|
* |
96
|
|
|
* @param integer $index the position. |
97
|
|
|
* @param mixed $data the data to be stored. |
98
|
|
|
*/ |
99
|
|
|
private function insertAt($index, $data) { |
100
|
|
|
$newNode = new SimpleLinkedListNode($data); |
101
|
|
|
$current = $this->head; |
102
|
|
|
$prev = null; |
103
|
|
|
$i = 0; |
104
|
|
|
while($i < $index) { |
105
|
|
|
$prev = $current; |
106
|
|
|
$current = $current->next; |
107
|
|
|
$i++; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
$prev->next = &$newNode; |
111
|
|
|
$newNode->next = &$current; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Adds at the end of the list new node containing |
116
|
|
|
* the data to be stored. |
117
|
|
|
* |
118
|
|
|
* @param mixed $data The data |
119
|
|
|
*/ |
120
|
|
|
public function push($data) { |
121
|
|
|
$this->insert($this->size, $data); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Adds at the beginning a node in the list. |
126
|
|
|
* |
127
|
|
|
* @param mixed $data |
128
|
|
|
* @return mixed the data stored. |
129
|
|
|
*/ |
130
|
|
|
public function unshift($data) { |
131
|
|
|
$this->insert(0, $data); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Returns the last node data with O(1). |
136
|
|
|
* |
137
|
|
|
* @return mixed null if the list is empty. |
138
|
|
|
*/ |
139
|
|
|
public function getLast() { |
140
|
|
|
$node = $this->searchLast(); |
141
|
|
|
|
142
|
|
|
return $node !== null ? $node->data : null; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Returns the last node with O(1). |
147
|
|
|
* |
148
|
|
|
* @return mixed null if the list is empty. |
149
|
|
|
*/ |
150
|
|
|
protected function searchLast() { |
151
|
|
|
if($this->head === null) { |
152
|
|
|
return null; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
return $this->tail; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Returns the data stored in the given position. |
160
|
|
|
* If index is 0 or size - 1 the method is O(1) else O(n). |
161
|
|
|
* |
162
|
|
|
* @param integer $index the position. |
163
|
|
|
* @throws OutOfBoundsException if it is out of limits (< 0 or > size - 1) |
164
|
|
|
* @return mixed the data stored in $index node. |
165
|
|
|
*/ |
166
|
|
View Code Duplication |
public function get($index) { |
|
|
|
|
167
|
|
|
$node = $this->search($index); |
168
|
|
|
if($node === null) { |
169
|
|
|
return null; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
return $node->data; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Returns the node stored in the given position. |
177
|
|
|
* If index is 0 or (size - 1) the method is O(1) else O(n). |
178
|
|
|
* |
179
|
|
|
* @param integer $index the position. |
180
|
|
|
* @throws OutOfBoundsException if it is out of limits (< 0 or > size - 1) |
181
|
|
|
* @return DataStructures\Lists\Nodes\SimpleLinkedListNode the node stored in $index. |
182
|
|
|
*/ |
183
|
|
|
protected function search($index) { |
184
|
|
|
if($index < 0 || $index > $this->size - 1) { |
185
|
|
|
throw new OutOfBoundsException(); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
if($index === 0) { |
189
|
|
|
return $this->head; |
|
|
|
|
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
if($index === $this->size - 1) { |
193
|
|
|
return $this->searchLast(); |
|
|
|
|
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
$i = 0; |
197
|
|
|
$current = $this->head; |
198
|
|
|
while($i < $index) { |
199
|
|
|
$current = $current->next; |
200
|
|
|
$i++; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
return $current; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* |
208
|
|
|
*/ |
209
|
|
|
public function contains($data) : bool { |
210
|
|
|
if($this->size === 0) { |
211
|
|
|
return false; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
$current = $this->head->next; |
215
|
|
|
$prev = $this->head; |
216
|
|
|
while($current !== $this->head) { |
217
|
|
|
if($prev->data === $data) { |
218
|
|
|
return true; |
219
|
|
|
} |
220
|
|
|
$prev = $current; |
221
|
|
|
$current = $current->next; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
return false; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* |
229
|
|
|
*/ |
230
|
|
View Code Duplication |
public function indexOf($data) { |
|
|
|
|
231
|
|
|
if($this->head === null) { |
232
|
|
|
return false; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
$current = $this->head; |
236
|
|
|
$i = 0; |
237
|
|
|
|
238
|
|
|
while($i < $this->size) { |
239
|
|
|
if($current->data == $data) { |
240
|
|
|
return $i; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
$current = $current->next; |
244
|
|
|
$i++; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
return false; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* |
252
|
|
|
*/ |
253
|
|
View Code Duplication |
public function lastIndexOf($data) { |
|
|
|
|
254
|
|
|
if($this->head === null) { |
255
|
|
|
return false; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
$current = $this->head; |
259
|
|
|
$i = 0; |
260
|
|
|
$pos = false; |
261
|
|
|
while($i < $this->size) { |
262
|
|
|
if($current->data == $data) { |
263
|
|
|
$pos = $i; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
$current = $current->next; |
267
|
|
|
$i++; |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
return $pos; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Generator for retrieve all nodes stored. |
275
|
|
|
* |
276
|
|
|
* @return null if the head is null (or list is empty) |
277
|
|
|
*/ |
278
|
|
View Code Duplication |
public function getAll() { |
|
|
|
|
279
|
|
|
if($this->head === null) { |
280
|
|
|
return; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
if($this->head->next === $this->tail) { |
284
|
|
|
yield $this->head->data; |
285
|
|
|
} else { |
286
|
|
|
$current = $this->head; |
287
|
|
|
$i = 0; |
288
|
|
|
while($i < $this->size) { |
289
|
|
|
yield $current->data; |
290
|
|
|
$current = $current->next; |
291
|
|
|
$i++; |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Delete a node in the given position and returns it back. |
298
|
|
|
* |
299
|
|
|
* @param integer $index the position. |
300
|
|
|
* @throws OutOfBoundsException if index is negative |
301
|
|
|
* or is greater than the size of the list. |
302
|
|
|
*/ |
303
|
|
View Code Duplication |
public function delete($index) { |
|
|
|
|
304
|
|
|
if($index < 0 || ($index > 0 && $index > $this->size - 1)) { |
305
|
|
|
throw new OutOfBoundsException(); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
// if the list is empty |
309
|
|
|
if($this->head === null) { |
310
|
|
|
return null; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
// if only there is an element |
314
|
|
|
if($this->head->next === $this->head) { |
315
|
|
|
$temp = $this->head; |
316
|
|
|
$this->head = null; |
317
|
|
|
$this->size--; |
318
|
|
|
|
319
|
|
|
return $temp->data; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
if($index === 0) { |
323
|
|
|
return $this->deleteBeginning(); |
324
|
|
|
} else if($index === $this->size - 1) { |
325
|
|
|
return $this->deleteEnd(); |
326
|
|
|
} else { |
327
|
|
|
return $this->deleteAt($index); |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
/** |
332
|
|
|
* Deletes at the beginnig of the list and returns the data stored. |
333
|
|
|
* |
334
|
|
|
* @return mixed the data stored in the node. |
335
|
|
|
*/ |
336
|
|
View Code Duplication |
private function deleteBeginning() { |
|
|
|
|
337
|
|
|
$temp = $this->head; |
338
|
|
|
$this->head = &$this->head->next; |
339
|
|
|
$this->tail->next = &$this->head; |
340
|
|
|
$this->size--; |
341
|
|
|
|
342
|
|
|
return $temp->data; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Deletes at the specified position and returns the data stored. |
347
|
|
|
* |
348
|
|
|
* @param integer $index the position. |
349
|
|
|
* @return mixed the data stored in the node. |
350
|
|
|
*/ |
351
|
|
|
private function deleteAt($index) { |
352
|
|
|
$i = 0; |
353
|
|
|
$prev = $this->head; |
354
|
|
|
$current = $this->head; |
355
|
|
|
|
356
|
|
|
while($i < $index) { |
357
|
|
|
$prev = $current; |
358
|
|
|
$current = $current->next; |
359
|
|
|
$i++; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
$prev->next = &$current->next; |
363
|
|
|
$this->size--; |
364
|
|
|
|
365
|
|
|
return $current->data; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* Deletes at the end of the list and returns the data stored. |
370
|
|
|
* |
371
|
|
|
* @return mixed the data stored in the node. |
372
|
|
|
*/ |
373
|
|
View Code Duplication |
private function deleteEnd() { |
|
|
|
|
374
|
|
|
$prev = $this->head; |
375
|
|
|
$current = $this->head; |
376
|
|
|
|
377
|
|
|
while($current !== $this->tail) { |
378
|
|
|
$prev = $current; |
379
|
|
|
$current = $current->next; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
$temp = $current; |
383
|
|
|
$prev->next = &$this->head; |
384
|
|
|
$this->tail = &$prev; |
385
|
|
|
$current = null; |
|
|
|
|
386
|
|
|
|
387
|
|
|
$this->size--; |
388
|
|
|
|
389
|
|
|
return $temp->data; |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
/** |
393
|
|
|
* Deletes the first node of the list and returns it. |
394
|
|
|
* |
395
|
|
|
* @return mixed the data. |
396
|
|
|
*/ |
397
|
|
|
public function shift() { |
398
|
|
|
return $this->delete(0); |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* Removes and returns the last node in the list. |
403
|
|
|
* |
404
|
|
|
* @return mixed data in node. |
405
|
|
|
*/ |
406
|
|
|
public function pop() { |
407
|
|
|
return $this->delete($this->size - 1); |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Removes all nodes of the list. It removes from the beginning. |
412
|
|
|
*/ |
413
|
|
|
public function clear() { |
414
|
|
|
while($this->head !== null) { |
415
|
|
|
$this->shift(); |
416
|
|
|
} |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
/** |
420
|
|
|
* Converts/exports the list content into array type. |
421
|
|
|
* |
422
|
|
|
* @return array data stored in all nodes. |
423
|
|
|
*/ |
424
|
|
|
public function toArray() : array { |
425
|
|
|
$arr = []; |
426
|
|
|
foreach($this->getAll() as $node) { |
427
|
|
|
$arr[] = $node; |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
return $arr; |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
/** |
434
|
|
|
* Reset the cursor position. |
435
|
|
|
*/ |
436
|
|
|
public function rewind() { |
437
|
|
|
$this->position = 0; |
438
|
|
|
$this->current = &$this->head; |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
/** |
442
|
|
|
* Returns the current node data. |
443
|
|
|
* |
444
|
|
|
* @return mixed |
445
|
|
|
*/ |
446
|
|
|
public function current() { |
447
|
|
|
return $this->current->data; |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
/** |
451
|
|
|
* Key or index that indicates the cursor position. |
452
|
|
|
* |
453
|
|
|
* @return integer The current position. |
454
|
|
|
*/ |
455
|
|
|
public function key() { |
456
|
|
|
return $this->position; |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* Move the cursor to the next node and increments the |
461
|
|
|
* position counter. |
462
|
|
|
*/ |
463
|
|
|
public function next() { |
464
|
|
|
++$this->position; |
465
|
|
|
$this->current = $this->current->next; |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
/** |
469
|
|
|
* Returns if the current pointer position is valid. |
470
|
|
|
* |
471
|
|
|
* @return boolean true if pointer is not last, else false. |
472
|
|
|
*/ |
473
|
|
|
public function valid() { |
474
|
|
|
return $this->position < $this->size; |
475
|
|
|
} |
476
|
|
|
|
477
|
|
|
/** |
478
|
|
|
* Binds to count() method. This is equal to make $this->tree->size(). |
479
|
|
|
* |
480
|
|
|
* @return integer the tree size. 0 if it is empty. |
481
|
|
|
*/ |
482
|
|
|
public function count() { |
483
|
|
|
return $this->size; |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
/** |
487
|
|
|
* Returns the array size. |
488
|
|
|
* |
489
|
|
|
* @return int the length |
490
|
|
|
*/ |
491
|
|
|
public function size() : int { |
492
|
|
|
return $this->size; |
493
|
|
|
} |
494
|
|
|
|
495
|
|
|
/** |
496
|
|
|
* Checks if the list is empty. |
497
|
|
|
* |
498
|
|
|
* @return boolean true if is empty, else false. |
499
|
|
|
*/ |
500
|
|
|
public function empty() : bool { |
501
|
|
|
return $this->size === 0; |
502
|
|
|
} |
503
|
|
|
} |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.