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 as Node; |
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 CountTrait, 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
|
|
|
|
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 Node($data); |
69
|
|
View Code Duplication |
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 Node($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 Node($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
|
|
|
if($this->head === null) { |
141
|
|
|
return null; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
return $this->tail->data; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Returns the last node with O(1). |
149
|
|
|
* |
150
|
|
|
* @return mixed null if the list is empty. |
151
|
|
|
*/ |
152
|
|
|
protected function searchLast() { |
153
|
|
|
if($this->head === null) { |
154
|
|
|
return null; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
return $this->tail; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* Returns the data stored in the given position. |
162
|
|
|
* If index is 0 or size - 1 the method is O(1) else O(n). |
163
|
|
|
* |
164
|
|
|
* @param integer $index the position. |
165
|
|
|
* @throws OutOfBoundsException if it is out of limits (< 0 or > size - 1) |
166
|
|
|
* @return mixed the data stored in $index node. |
167
|
|
|
*/ |
168
|
|
View Code Duplication |
public function get($index) { |
|
|
|
|
169
|
|
|
if($index < 0 || $index > $this->size - 1) { |
170
|
|
|
throw new OutOfBoundsException(); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
if($index === 0) { |
174
|
|
|
return $this->head->data; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
if($index === $this->size - 1) { |
178
|
|
|
return $this->getLast(); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
$i = 0; |
182
|
|
|
$current = $this->head; |
183
|
|
|
while($i < $index) { |
184
|
|
|
$current = $current->next; |
185
|
|
|
$i++; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
return $current->data; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Returns the node stored in the given position. |
193
|
|
|
* If index is 0 or (size - 1) the method is O(1) else O(n). |
194
|
|
|
* |
195
|
|
|
* @param integer $index the position. |
196
|
|
|
* @throws OutOfBoundsException if it is out of limits (< 0 or > size - 1) |
197
|
|
|
* @return DataStructures\Lists\Nodes\SimpleLinkedListNode|null the node stored in $index. |
198
|
|
|
*/ |
199
|
|
View Code Duplication |
protected function search($index) { |
|
|
|
|
200
|
|
|
if($index < 0 || $index > $this->size - 1) { |
201
|
|
|
throw new OutOfBoundsException(); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
if($index === 0) { |
205
|
|
|
return $this->head; |
|
|
|
|
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
if($index === $this->size - 1) { |
209
|
|
|
return $this->getLast(); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
$i = 0; |
213
|
|
|
$current = $this->head; |
214
|
|
|
while($i < $index) { |
215
|
|
|
$current = $current->next; |
216
|
|
|
$i++; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
return $current; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* Generator for retrieve all nodes stored. |
224
|
|
|
* |
225
|
|
|
* @return null if the head is null (or list is empty) |
226
|
|
|
*/ |
227
|
|
View Code Duplication |
public function getAll() { |
|
|
|
|
228
|
|
|
if($this->head === null) { |
229
|
|
|
return; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
if($this->head->next === $this->tail) { |
233
|
|
|
yield $this->head->data; |
234
|
|
|
} else { |
235
|
|
|
$current = $this->head; |
236
|
|
|
$i = 0; |
237
|
|
|
while($i < $this->size) { |
238
|
|
|
yield $current->data; |
239
|
|
|
$current = $current->next; |
240
|
|
|
$i++; |
241
|
|
|
} |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Delete a node in the given position and returns it back. |
247
|
|
|
* |
248
|
|
|
* @param integer $index the position. |
249
|
|
|
* @throws OutOfBoundsException if index is negative |
250
|
|
|
* or is greater than the size of the list. |
251
|
|
|
*/ |
252
|
|
View Code Duplication |
public function delete($index) { |
|
|
|
|
253
|
|
|
if($index < 0 || ($index > 0 && $index > $this->size - 1)) { |
254
|
|
|
throw new OutOfBoundsException(); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
// if the list is empty |
258
|
|
|
if($this->head === null) { |
259
|
|
|
return null; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
// if only there is an element |
263
|
|
|
if($this->head->next === $this->head) { |
264
|
|
|
$temp = $this->head; |
265
|
|
|
$this->head = null; |
266
|
|
|
$this->size--; |
267
|
|
|
|
268
|
|
|
return $temp->data; |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
if($index === 0) { |
272
|
|
|
return $this->deleteBeginning(); |
273
|
|
|
} else if($index === $this->size - 1) { |
274
|
|
|
return $this->deleteEnd(); |
275
|
|
|
} else { |
276
|
|
|
return $this->deleteAt($index); |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Deletes at the beginnig of the list and returns the data stored. |
282
|
|
|
* |
283
|
|
|
* @return mixed the data stored in the node. |
284
|
|
|
*/ |
285
|
|
View Code Duplication |
private function deleteBeginning() { |
|
|
|
|
286
|
|
|
$temp = $this->head; |
287
|
|
|
$this->head = &$this->head->next; |
288
|
|
|
$this->tail->next = &$this->head; |
289
|
|
|
$this->size--; |
290
|
|
|
|
291
|
|
|
return $temp->data; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Deletes at the specified position and returns the data stored. |
296
|
|
|
* |
297
|
|
|
* @param integer $index the position. |
298
|
|
|
* @return mixed the data stored in the node. |
299
|
|
|
*/ |
300
|
|
View Code Duplication |
private function deleteAt($index) { |
|
|
|
|
301
|
|
|
$i = 0; |
302
|
|
|
$prev = $this->head; |
303
|
|
|
$current = $this->head; |
304
|
|
|
|
305
|
|
|
while($i < $index) { |
306
|
|
|
$prev = $current; |
307
|
|
|
$current = $current->next; |
308
|
|
|
$i++; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
$temp = $current; |
312
|
|
|
$prev->next = &$current->next; |
313
|
|
|
$current = null; |
|
|
|
|
314
|
|
|
$this->size--; |
315
|
|
|
|
316
|
|
|
return $temp->data; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* Deletes at the end of the list and returns the data stored. |
321
|
|
|
* |
322
|
|
|
* @return mixed the data stored in the node. |
323
|
|
|
*/ |
324
|
|
View Code Duplication |
private function deleteEnd() { |
|
|
|
|
325
|
|
|
$prev = $this->head; |
326
|
|
|
$current = $this->head; |
327
|
|
|
|
328
|
|
|
while($current !== $this->tail) { |
329
|
|
|
$prev = $current; |
330
|
|
|
$current = $current->next; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
$temp = $current; |
334
|
|
|
$prev->next = &$this->head; |
335
|
|
|
$this->tail = &$prev; |
336
|
|
|
$current = null; |
|
|
|
|
337
|
|
|
|
338
|
|
|
$this->size--; |
339
|
|
|
|
340
|
|
|
return $temp->data; |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* Deletes the first node of the list and returns it. |
345
|
|
|
* |
346
|
|
|
* @return mixed the data. |
347
|
|
|
*/ |
348
|
|
|
public function shift() { |
349
|
|
|
return $this->delete(0); |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* Removes and returns the last node in the list. |
354
|
|
|
* |
355
|
|
|
* @return mixed data in node. |
356
|
|
|
*/ |
357
|
|
|
public function pop() { |
358
|
|
|
return $this->delete($this->size - 1); |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Removes all nodes of the list. It removes from the beginning. |
363
|
|
|
*/ |
364
|
|
|
public function clear() { |
365
|
|
|
while($this->head !== null) { |
366
|
|
|
$this->shift(); |
367
|
|
|
} |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* Converts/exports the list content into array type. |
372
|
|
|
* |
373
|
|
|
* @return array data stored in all nodes. |
374
|
|
|
*/ |
375
|
|
|
public function toArray() : array { |
376
|
|
|
$arr = []; |
377
|
|
|
foreach($this->getAll() as $node) { |
378
|
|
|
$arr[] = $node; |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
return $arr; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* Reset the cursor position. |
386
|
|
|
*/ |
387
|
|
|
public function rewind() { |
388
|
|
|
$this->position = 0; |
389
|
|
|
$this->current = &$this->head; |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
/** |
393
|
|
|
* Returns the current node data. |
394
|
|
|
* |
395
|
|
|
* @return mixed |
396
|
|
|
*/ |
397
|
|
|
public function current() { |
398
|
|
|
return $this->current->data; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* Key or index that indicates the cursor position. |
403
|
|
|
* |
404
|
|
|
* @return integer The current position. |
405
|
|
|
*/ |
406
|
|
|
public function key() { |
407
|
|
|
return $this->position; |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Move the cursor to the next node and increments the |
412
|
|
|
* position counter. |
413
|
|
|
*/ |
414
|
|
|
public function next() { |
415
|
|
|
++$this->position; |
416
|
|
|
$this->current = $this->current->next; |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
/** |
420
|
|
|
* Returns if the current pointer position is valid. |
421
|
|
|
* |
422
|
|
|
* @return boolean true if pointer is not last, else false. |
423
|
|
|
*/ |
424
|
|
|
public function valid() { |
425
|
|
|
return $this->position < $this->size; |
426
|
|
|
} |
427
|
|
|
} |
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.