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\ListAbstract; |
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 extends ListAbstract { |
25
|
|
|
use ArrayAccessTrait; |
26
|
|
|
protected $head; |
27
|
|
|
private $tail; |
28
|
|
|
private $current; |
29
|
|
|
private $position; |
30
|
|
|
|
31
|
|
View Code Duplication |
public function __construct() { |
|
|
|
|
32
|
|
|
$this->head = null; |
33
|
|
|
$this->tail = &$this->head; |
34
|
|
|
$this->size = 0; |
35
|
|
|
$this->position = 0; |
36
|
|
|
$this->current = &$this->head; |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Inserts at the beginning of the list. |
42
|
|
|
* |
43
|
|
|
* @param mixed $data |
44
|
|
|
*/ |
45
|
|
|
protected function insertBeginning($data) { |
46
|
|
|
$newNode = new SimpleLinkedListNode($data); |
47
|
|
|
if($this->head === null) { |
48
|
|
|
$newNode->next = &$this->head; |
49
|
|
|
$this->head = &$newNode; |
50
|
|
|
$this->tail = &$newNode; |
51
|
|
|
} else { |
52
|
|
|
$this->tail->next = &$newNode; |
53
|
|
|
$newNode->next = $this->head; |
54
|
|
|
$this->head = &$newNode; |
55
|
|
|
} |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Add a new node in the specified index. |
60
|
|
|
* |
61
|
|
|
* @param integer $index the position. |
|
|
|
|
62
|
|
|
* @param mixed $data the data to be stored. |
63
|
|
|
*/ |
64
|
|
View Code Duplication |
protected function insertEnd($data) { |
|
|
|
|
65
|
|
|
$newNode = new SimpleLinkedListNode($data); |
66
|
|
|
$this->tail->next = &$newNode; |
67
|
|
|
$newNode->next = &$this->head; |
68
|
|
|
$this->tail = &$newNode; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Add a new node in the specified index. |
73
|
|
|
* |
74
|
|
|
* @param integer $index the position. |
75
|
|
|
* @param mixed $data the data to be stored. |
76
|
|
|
*/ |
77
|
|
View Code Duplication |
protected function insertAt($index, $data) { |
|
|
|
|
78
|
|
|
$newNode = new SimpleLinkedListNode($data); |
79
|
|
|
$current = $this->head; |
80
|
|
|
$prev = null; |
81
|
|
|
$i = 0; |
82
|
|
|
while($i < $index) { |
83
|
|
|
$prev = $current; |
84
|
|
|
$current = $current->next; |
85
|
|
|
$i++; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
$prev->next = &$newNode; |
89
|
|
|
$newNode->next = &$current; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Returns the last node data with O(1). |
94
|
|
|
* |
95
|
|
|
* @return mixed null if the list is empty. |
96
|
|
|
*/ |
97
|
|
|
public function getLast() { |
98
|
|
|
$node = $this->searchLast(); |
99
|
|
|
|
100
|
|
|
return $node !== null ? $node->data : null; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Returns the last node with O(1). |
105
|
|
|
* |
106
|
|
|
* @return mixed null if the list is empty. |
107
|
|
|
*/ |
108
|
|
|
protected function searchLast() { |
109
|
|
|
if($this->head === null) { |
110
|
|
|
return null; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
return $this->tail; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Returns the data stored in the given position. |
118
|
|
|
* If index is 0 or size - 1 the method is O(1) else O(n). |
119
|
|
|
* |
120
|
|
|
* @param integer $index the position. |
121
|
|
|
* @throws OutOfBoundsException if it is out of limits (< 0 or > size - 1) |
122
|
|
|
* @return mixed the data stored in $index node. |
123
|
|
|
*/ |
124
|
|
View Code Duplication |
public function get($index) { |
|
|
|
|
125
|
|
|
$node = $this->search($index); |
126
|
|
|
if($node === null) { |
127
|
|
|
return null; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
return $node->data; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Returns the node stored in the given position. |
135
|
|
|
* If index is 0 or (size - 1) the method is O(1) else O(n). |
136
|
|
|
* |
137
|
|
|
* @param integer $index the position. |
138
|
|
|
* @throws OutOfBoundsException if it is out of limits (< 0 or > size - 1) |
139
|
|
|
* @return DataStructures\Lists\Nodes\SimpleLinkedListNode the node stored in $index. |
140
|
|
|
*/ |
141
|
|
|
protected function search($index) { |
142
|
|
|
if($index < 0 || $index > $this->size - 1) { |
143
|
|
|
throw new OutOfBoundsException(); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
if($index === 0) { |
147
|
|
|
return $this->head; |
|
|
|
|
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
if($index === $this->size - 1) { |
151
|
|
|
return $this->searchLast(); |
|
|
|
|
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
$i = 0; |
155
|
|
|
$current = $this->head; |
156
|
|
|
while($i < $index) { |
157
|
|
|
$current = $current->next; |
158
|
|
|
$i++; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
return $current; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* {@inheritDoc} |
166
|
|
|
*/ |
167
|
|
|
public function contains($data) : bool { |
168
|
|
|
if($this->size === 0) { |
169
|
|
|
return false; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
$current = $this->head->next; |
173
|
|
|
$prev = $this->head; |
174
|
|
|
while($current !== $this->head) { |
175
|
|
|
if($prev->data === $data) { |
176
|
|
|
return true; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
$prev = $current; |
180
|
|
|
$current = $current->next; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
if($prev->data === $data) { |
184
|
|
|
return true; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
return false; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* {@inheritDoc} |
192
|
|
|
*/ |
193
|
|
View Code Duplication |
public function indexOf($data) { |
|
|
|
|
194
|
|
|
if($this->head === null) { |
195
|
|
|
return false; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
$current = $this->head; |
199
|
|
|
$i = 0; |
200
|
|
|
|
201
|
|
|
while($i < $this->size) { |
202
|
|
|
if($current->data === $data) { |
203
|
|
|
return $i; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
$current = $current->next; |
207
|
|
|
$i++; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
return false; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* {@inheritDoc} |
215
|
|
|
*/ |
216
|
|
View Code Duplication |
public function lastIndexOf($data) { |
|
|
|
|
217
|
|
|
if($this->head === null) { |
218
|
|
|
return false; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
$current = $this->head; |
222
|
|
|
$i = 0; |
223
|
|
|
$pos = false; |
224
|
|
|
while($i < $this->size) { |
225
|
|
|
if($current->data === $data) { |
226
|
|
|
$pos = $i; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
$current = $current->next; |
230
|
|
|
$i++; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
return $pos; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* {@inheritDoc} |
238
|
|
|
*/ |
239
|
|
|
public function remove($data) { |
240
|
|
|
$current = $this->head; |
241
|
|
|
$prev = $this->tail; |
242
|
|
|
$i = 0; |
243
|
|
|
|
244
|
|
|
if($this->head === null) { |
245
|
|
|
return null; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
if($this->head->data === $data) { |
249
|
|
|
$this->head = &$this->head->next; |
250
|
|
|
$this->tail->next = &$this->head; |
251
|
|
|
$this->size--; |
252
|
|
|
return $current->data; |
253
|
|
|
} |
254
|
|
View Code Duplication |
while($i < $this->size) { |
|
|
|
|
255
|
|
|
if($prev->data === $data) { |
256
|
|
|
$prev->next = &$current->next; |
257
|
|
|
$this->size--; |
258
|
|
|
|
259
|
|
|
return $current->data; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
$prev = $current; |
263
|
|
|
$current = $current->next; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
return null; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Generator for retrieve all nodes stored. |
271
|
|
|
* |
272
|
|
|
* @return null if the head is null (or list is empty) |
273
|
|
|
*/ |
274
|
|
View Code Duplication |
public function getAll() { |
|
|
|
|
275
|
|
|
if($this->head === null) { |
276
|
|
|
return; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
if($this->head->next === $this->tail) { |
280
|
|
|
yield $this->head->data; |
281
|
|
|
} else { |
282
|
|
|
$current = $this->head; |
283
|
|
|
$i = 0; |
284
|
|
|
while($i < $this->size) { |
285
|
|
|
yield $current->data; |
286
|
|
|
$current = $current->next; |
287
|
|
|
$i++; |
288
|
|
|
} |
289
|
|
|
} |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Deletes at the beginnig of the list and returns the data stored. |
294
|
|
|
* |
295
|
|
|
* @return mixed the data stored in the node. |
296
|
|
|
*/ |
297
|
|
View Code Duplication |
protected function deleteBeginning() { |
|
|
|
|
298
|
|
|
// if only there is an element |
299
|
|
|
if($this->head->next === $this->head) { |
300
|
|
|
$temp = $this->head; |
301
|
|
|
$this->head = null; |
302
|
|
|
return $temp->data; |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
$temp = $this->head; |
306
|
|
|
$this->head = &$this->head->next; |
307
|
|
|
$this->tail->next = &$this->head; |
308
|
|
|
|
309
|
|
|
return $temp->data; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Deletes at the specified position and returns the data stored. |
314
|
|
|
* |
315
|
|
|
* @param integer $index the position. |
316
|
|
|
* @return mixed the data stored in the node. |
317
|
|
|
*/ |
318
|
|
|
protected function deleteAt($index) { |
319
|
|
|
$i = 0; |
320
|
|
|
$prev = $this->head; |
321
|
|
|
$current = $this->head; |
322
|
|
|
|
323
|
|
|
while($i < $index) { |
324
|
|
|
$prev = $current; |
325
|
|
|
$current = $current->next; |
326
|
|
|
$i++; |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
$prev->next = &$current->next; |
330
|
|
|
|
331
|
|
|
return $current->data; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* Deletes at the end of the list and returns the data stored. |
336
|
|
|
* |
337
|
|
|
* @return mixed the data stored in the node. |
338
|
|
|
*/ |
339
|
|
View Code Duplication |
protected function deleteEnd() { |
|
|
|
|
340
|
|
|
$prev = $this->head; |
341
|
|
|
$current = $this->head; |
342
|
|
|
|
343
|
|
|
while($current !== $this->tail) { |
344
|
|
|
$prev = $current; |
345
|
|
|
$current = $current->next; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
$temp = $current; |
349
|
|
|
$prev->next = &$this->head; |
350
|
|
|
$this->tail = &$prev; |
351
|
|
|
$current = null; |
|
|
|
|
352
|
|
|
|
353
|
|
|
return $temp->data; |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* Converts/exports the list content into array type. |
358
|
|
|
* |
359
|
|
|
* @return array data stored in all nodes. |
360
|
|
|
*/ |
361
|
|
|
public function toArray() : array { |
362
|
|
|
$arr = []; |
363
|
|
|
foreach($this->getAll() as $node) { |
364
|
|
|
$arr[] = $node; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
return $arr; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
public function clear() { |
371
|
|
|
while($this->head !== null) { |
372
|
|
|
$this->shift(); |
373
|
|
|
} |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
/** |
377
|
|
|
* Reset the cursor position. |
378
|
|
|
*/ |
379
|
|
|
public function rewind() { |
380
|
|
|
$this->position = 0; |
381
|
|
|
$this->current = &$this->head; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* Returns the current node data. |
386
|
|
|
* |
387
|
|
|
* @return mixed |
388
|
|
|
*/ |
389
|
|
|
public function current() { |
390
|
|
|
return $this->current->data; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* Key or index that indicates the cursor position. |
395
|
|
|
* |
396
|
|
|
* @return integer The current position. |
397
|
|
|
*/ |
398
|
|
|
public function key() { |
399
|
|
|
return $this->position; |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* Move the cursor to the next node and increments the |
404
|
|
|
* position counter. |
405
|
|
|
*/ |
406
|
|
|
public function next() { |
407
|
|
|
++$this->position; |
408
|
|
|
$this->current = $this->current->next; |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
/** |
412
|
|
|
* Returns if the current pointer position is valid. |
413
|
|
|
* |
414
|
|
|
* @return boolean true if pointer is not last, else false. |
415
|
|
|
*/ |
416
|
|
|
public function valid() { |
417
|
|
|
return $this->position < $this->size; |
418
|
|
|
} |
419
|
|
|
} |
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.