Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
40 / 40 |
|
100.00% |
15 / 15 |
CRAP | |
100.00% |
1 / 1 |
Collection | |
100.00% |
40 / 40 |
|
100.00% |
15 / 15 |
26 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
count | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isEmpty | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
validateKey | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
validateExistingKey | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
validateNewKey | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getElement | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
getElements | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getKey | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getKeys | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
add | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
update | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
delete | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
getIndex | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setIndex | |
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 ArrayIterator; |
12 | use IteratorIterator; |
13 | use pvc\interfaces\struct\collection\CollectionInterface; |
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 | * @extends IteratorIterator<non-negative-int, ElementType, ArrayIterator> |
22 | * @implements CollectionInterface<ElementType> |
23 | * |
24 | * elements in a collection cannot be null |
25 | */ |
26 | class Collection extends IteratorIterator implements CollectionInterface |
27 | { |
28 | /** |
29 | * @var ArrayIterator<int, ElementType> |
30 | */ |
31 | protected ArrayIterator $iterator; |
32 | |
33 | /** |
34 | * @var callable(ElementType, ElementType):int|null |
35 | * used by getElements to return the elements in whatever sort order you choose |
36 | */ |
37 | protected $comparator = null; |
38 | |
39 | /** |
40 | * @param array<non-negative-int, ElementType> $elements |
41 | * @param callable(ElementType, ElementType):int|null $comparator |
42 | */ |
43 | public function __construct(array $elements = [], callable|null $comparator = null) |
44 | { |
45 | $this->iterator = new ArrayIterator($elements); |
46 | $this->comparator = $comparator; |
47 | parent::__construct($this->iterator); |
48 | } |
49 | |
50 | /** |
51 | * count |
52 | * @return non-negative-int |
53 | */ |
54 | public function count(): int |
55 | { |
56 | return $this->iterator->count(); |
57 | } |
58 | |
59 | /** |
60 | * isEmpty returns whether the collection is empty or not |
61 | * @return bool |
62 | */ |
63 | public function isEmpty(): bool |
64 | { |
65 | return (0 == $this->count()); |
66 | } |
67 | |
68 | /** |
69 | * validateKey encapsulates the logic that all keys must be non-negative integers |
70 | * @param int $key |
71 | * @return bool |
72 | */ |
73 | protected function validateKey(int $key): bool |
74 | { |
75 | return $key >= 0; |
76 | } |
77 | |
78 | /** |
79 | * validateExistingKey ensures that the key is both valid and exists in the collection |
80 | * @param int $key |
81 | * @throws InvalidKeyException |
82 | * @throws NonExistentKeyException |
83 | */ |
84 | public function validateExistingKey(int $key): bool |
85 | { |
86 | return $this->iterator->offsetExists($key); |
87 | } |
88 | |
89 | /** |
90 | * validateNewKey ensures that the key does not exist in the collection |
91 | * @param int $key |
92 | */ |
93 | public function validateNewKey(int $key): bool |
94 | { |
95 | return !$this->iterator->offsetExists($key); |
96 | } |
97 | |
98 | /** |
99 | * getElement |
100 | * @param non-negative-int $key |
101 | * @return ElementType |
102 | */ |
103 | public function getElement(int $key): mixed |
104 | { |
105 | if (!$this->validateKey($key)) { |
106 | throw new InvalidKeyException($key); |
107 | } |
108 | if (!$this->validateExistingKey($key)) { |
109 | throw new NonExistentKeyException($key); |
110 | } |
111 | /** |
112 | * element cannot be null |
113 | */ |
114 | $element = $this->iterator->offsetGet($key); |
115 | assert(!is_null($element)); |
116 | return $element; |
117 | } |
118 | |
119 | /** |
120 | * now implement methods explicitly defined in the interface |
121 | */ |
122 | |
123 | /** |
124 | * @function getElements |
125 | * @return array<ElementType> |
126 | */ |
127 | public function getElements(): array |
128 | { |
129 | if ($this->comparator) { |
130 | $this->iterator->uasort($this->comparator); |
131 | } |
132 | return iterator_to_array($this->iterator); |
133 | } |
134 | |
135 | /** |
136 | * @function getKey |
137 | * @param ElementType $element |
138 | * @param bool $strict |
139 | * @return non-negative-int|false |
140 | */ |
141 | public function getKey($element, bool $strict = true): int|false |
142 | { |
143 | $key = array_search($element, $this->getElements(), $strict); |
144 | assert((is_int($key) && ($key >= 0)) || $key === false); |
145 | return $key; |
146 | } |
147 | |
148 | /** |
149 | * getKeys returns all the keys in the collection where the corresponding element equals $element. |
150 | * |
151 | * @param ElementType $element |
152 | * @param bool $strict |
153 | * @return array<non-negative-int> |
154 | */ |
155 | public function getKeys($element, bool $strict = true): array |
156 | { |
157 | /** @var array<non-negative-int> $keys */ |
158 | $keys = array_keys($this->getElements(), $element, $strict); |
159 | return $keys; |
160 | } |
161 | |
162 | /** |
163 | * add |
164 | * |
165 | * Unlike when you are dealing with a raw array, using an existing key will throw an exception instead |
166 | * of overwriting an existing entry in the array. Use update to be explicit about updating an entry. |
167 | * |
168 | * @param non-negative-int $key |
169 | * @param ElementType $element |
170 | * @throws DuplicateKeyException |
171 | * @throws InvalidKeyException |
172 | */ |
173 | public function add(int $key, $element): void |
174 | { |
175 | if (!$this->validateKey($key)) { |
176 | throw new InvalidKeyException($key); |
177 | } |
178 | if (!$this->validateNewKey($key)) { |
179 | throw new DuplicateKeyException($key); |
180 | } |
181 | $this->iterator->offsetSet($key, $element); |
182 | } |
183 | |
184 | /** |
185 | * update assigns a new element to the entry with index $key |
186 | * |
187 | * @param non-negative-int $key |
188 | * @param ElementType $element |
189 | * @throws InvalidKeyException |
190 | * @throws NonExistentKeyException |
191 | */ |
192 | public function update(int $key, $element): void |
193 | { |
194 | if (!$this->validateKey($key)) { |
195 | throw new InvalidKeyException($key); |
196 | } |
197 | if (!$this->validateExistingKey($key)) { |
198 | throw new NonExistentKeyException($key); |
199 | } |
200 | $this->iterator->offsetSet($key, $element); |
201 | } |
202 | |
203 | /** |
204 | * delete removes an element from the collection. Unlike unset, this operation throws an exception if the |
205 | * key does not exist. |
206 | * |
207 | * @param non-negative-int $key |
208 | * @return void |
209 | * @throws InvalidKeyException |
210 | * @throws NonExistentKeyException |
211 | */ |
212 | public function delete(int $key): void |
213 | { |
214 | if (!$this->validateKey($key)) { |
215 | throw new InvalidKeyException($key); |
216 | } |
217 | if (!$this->validateExistingKey($key)) { |
218 | throw new NonExistentKeyException($key); |
219 | } |
220 | $this->iterator->offsetUnset($key); |
221 | } |
222 | |
223 | /** |
224 | * @param int $key |
225 | * @return int |
226 | */ |
227 | public function getIndex(int $key): int |
228 | { |
229 | return -1; |
230 | } |
231 | |
232 | public function setIndex(int $key, int $newIndex): void |
233 | { |
234 | } |
235 | } |