Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
42 / 42 |
|
100.00% |
19 / 19 |
CRAP | |
100.00% |
1 / 1 |
Collection | |
100.00% |
42 / 42 |
|
100.00% |
19 / 19 |
28 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setInnerIterator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setComparator | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getIndex | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
initialize | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isEmpty | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
count | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getElement | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
validateExistingKey | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
findElementKey | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getElements | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
findElementKeys | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
add | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
validateNewKey | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
update | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
delete | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getFirst | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getLast | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getNth | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | /** |
4 | * @author: Doug Wilbourne (dougwilbourne@gmail.com) |
5 | */ |
6 | |
7 | declare(strict_types=1); |
8 | |
9 | namespace pvc\struct\collection; |
10 | |
11 | use IteratorIterator; |
12 | use pvc\interfaces\struct\collection\CollectionInterface; |
13 | use pvc\interfaces\validator\ValTesterInterface; |
14 | use pvc\struct\collection\err\DuplicateKeyException; |
15 | use pvc\struct\collection\err\InvalidKeyException; |
16 | use pvc\struct\collection\err\NonExistentKeyException; |
17 | |
18 | /** |
19 | * Class Collection |
20 | * @template ElementType |
21 | * |
22 | * @extends IteratorIterator<non-negative-int, ElementType, ArrayIteratorNonNegIntKeys> |
23 | * @implements CollectionInterface<ElementType> |
24 | * |
25 | * elements in a collection cannot be null |
26 | */ |
27 | class Collection extends IteratorIterator implements CollectionInterface |
28 | { |
29 | /** |
30 | * @var ArrayIteratorNonNegIntKeys<ElementType> |
31 | */ |
32 | protected ArrayIteratorNonNegIntKeys $iterator; |
33 | |
34 | /** |
35 | * @var ?callable(ElementType, ElementType): int $comparator; |
36 | */ |
37 | protected $comparator; |
38 | |
39 | |
40 | /** |
41 | * @param array<non-negative-int, ElementType> $elements |
42 | */ |
43 | public function __construct( |
44 | array $elements = [], |
45 | ) { |
46 | $this->setInnerIterator($elements); |
47 | parent::__construct($this->iterator); |
48 | } |
49 | |
50 | /** |
51 | * @param array<non-negative-int, ElementType> $elements |
52 | * |
53 | * @return void |
54 | * this method is also used to initialize the collection by |
55 | * calling it with no parameters |
56 | */ |
57 | protected function setInnerIterator(array $elements = []): void |
58 | { |
59 | $this->iterator = new ArrayIteratorNonNegIntKeys($elements); |
60 | } |
61 | |
62 | /** |
63 | * @param ?callable(ElementType, ElementType): int $comparator |
64 | * |
65 | * @return void |
66 | */ |
67 | public function setComparator($comparator): void |
68 | { |
69 | $this->comparator = $comparator; |
70 | if ($this->comparator !== null) { |
71 | $this->iterator->uasort($this->comparator); |
72 | } |
73 | } |
74 | |
75 | /** |
76 | * @param non-negative-int $key |
77 | * |
78 | * @return non-negative-int|null |
79 | */ |
80 | public function getIndex(int $key): ?int |
81 | { |
82 | $i = 0; |
83 | foreach ($this->iterator as $n => $element) { |
84 | if ($n === $key) return $i; |
85 | $i++; |
86 | } |
87 | return null; |
88 | } |
89 | |
90 | public function initialize(): void |
91 | { |
92 | $this->setInnerIterator(); |
93 | } |
94 | |
95 | /** |
96 | * isEmpty returns whether the collection is empty or not |
97 | * |
98 | * @return bool |
99 | */ |
100 | public function isEmpty(): bool |
101 | { |
102 | return (0 == $this->count()); |
103 | } |
104 | |
105 | /** |
106 | * count |
107 | * |
108 | * @return non-negative-int |
109 | */ |
110 | public function count(): int |
111 | { |
112 | return $this->iterator->count(); |
113 | } |
114 | |
115 | /** |
116 | * getElement |
117 | * |
118 | * @param non-negative-int $key |
119 | * |
120 | * @return ElementType |
121 | */ |
122 | public function getElement(int $key): mixed |
123 | { |
124 | if (!$this->validateExistingKey($key)) { |
125 | throw new NonExistentKeyException($key); |
126 | } |
127 | /** |
128 | * element cannot be null |
129 | */ |
130 | $element = $this->iterator->offsetGet($key); |
131 | assert(!is_null($element)); |
132 | return $element; |
133 | } |
134 | |
135 | |
136 | /** |
137 | * validateExistingKey ensures that the key is both valid and exists in the collection |
138 | * |
139 | * @param non-negative-int $key |
140 | * |
141 | * @throws InvalidKeyException |
142 | * @throws NonExistentKeyException |
143 | */ |
144 | protected function validateExistingKey(int $key): bool |
145 | { |
146 | return $this->iterator->offsetExists($key); |
147 | } |
148 | |
149 | /** |
150 | * @function findElementKey |
151 | * |
152 | * @param ValTesterInterface<ElementType> $valTester |
153 | * |
154 | * @return non-negative-int|null |
155 | */ |
156 | public function findElementKey(ValTesterInterface $valTester): ?int |
157 | { |
158 | return array_find_key($this->getElements(), [$valTester, 'testValue']); |
159 | } |
160 | |
161 | /** |
162 | * now implement methods explicitly defined in the interface |
163 | */ |
164 | |
165 | /** |
166 | * @function getElements |
167 | * @return array<non-negative-int, ElementType> |
168 | */ |
169 | public function getElements(): array |
170 | { |
171 | return iterator_to_array($this->iterator); |
172 | } |
173 | |
174 | /** |
175 | * |
176 | * @param ValTesterInterface<ElementType> $valTester |
177 | * |
178 | * @return array<non-negative-int> |
179 | */ |
180 | public function findElementKeys(ValTesterInterface $valTester): array |
181 | { |
182 | $elements = array_filter($this->getElements(), [$valTester, 'testValue']); |
183 | return array_keys($elements); |
184 | } |
185 | |
186 | /** |
187 | * add |
188 | * |
189 | * Unlike when you are dealing with a raw array, using an existing key will throw an exception instead |
190 | * of overwriting an existing entry in the array. Use update to be explicit about updating an entry. |
191 | * |
192 | * @param non-negative-int $key |
193 | * @param ElementType $element |
194 | * |
195 | * @throws DuplicateKeyException |
196 | * @throws InvalidKeyException |
197 | */ |
198 | public function add(int $key, $element): void |
199 | { |
200 | if (!$this->validateNewKey($key)) { |
201 | throw new DuplicateKeyException($key); |
202 | } |
203 | $this->iterator->offsetSet($key, $element); |
204 | |
205 | if ($this->comparator !== null) { |
206 | $this->iterator->uasort($this->comparator); |
207 | } |
208 | } |
209 | |
210 | /** |
211 | * validateNewKey ensures that the key does not exist in the collection |
212 | * |
213 | * @param non-negative-int $key |
214 | */ |
215 | protected function validateNewKey(int $key): bool |
216 | { |
217 | return !$this->iterator->offsetExists($key); |
218 | } |
219 | |
220 | /** |
221 | * update assigns a new element to the entry with index $key |
222 | * |
223 | * @param non-negative-int $key |
224 | * @param ElementType $element |
225 | * |
226 | * @throws InvalidKeyException |
227 | * @throws NonExistentKeyException |
228 | */ |
229 | public function update(int $key, $element): void |
230 | { |
231 | if (!$this->validateExistingKey($key)) { |
232 | throw new NonExistentKeyException($key); |
233 | } |
234 | |
235 | $this->iterator->offsetSet($key, $element); |
236 | |
237 | if ($this->comparator !== null) { |
238 | $this->iterator->uasort($this->comparator); |
239 | } |
240 | |
241 | } |
242 | |
243 | /** |
244 | * delete removes an element from the collection. Unlike unset, this operation throws an exception if the |
245 | * key does not exist. |
246 | * |
247 | * @param non-negative-int $key |
248 | * |
249 | * @return void |
250 | * @throws InvalidKeyException |
251 | * @throws NonExistentKeyException |
252 | */ |
253 | public function delete(int $key): void |
254 | { |
255 | if (!$this->validateExistingKey($key)) { |
256 | throw new NonExistentKeyException($key); |
257 | } |
258 | $this->iterator->offsetUnset($key); |
259 | } |
260 | |
261 | /** |
262 | * @return ElementType|null |
263 | */ |
264 | public function getFirst(): mixed |
265 | { |
266 | return array_values($this->getElements())[0] ?? null; |
267 | } |
268 | |
269 | /** |
270 | * @return ElementType|null |
271 | */ |
272 | public function getLast(): mixed |
273 | { |
274 | return array_values($this->getElements())[count($this->getElements()) |
275 | - 1] ?? null; |
276 | } |
277 | |
278 | /** |
279 | * @param non-negative-int $index |
280 | * |
281 | * @return ElementType|null |
282 | */ |
283 | public function getNth(int $index): mixed |
284 | { |
285 | return array_values($this->getElements())[$index] ?? null; |
286 | } |
287 | |
288 | } |