1 | <?php |
||
2 | declare(strict_types = 1); |
||
3 | |||
4 | namespace Innmind\Immutable\Map; |
||
5 | |||
6 | use Innmind\Immutable\{ |
||
7 | Map, |
||
8 | Type, |
||
9 | Str, |
||
10 | Sequence, |
||
11 | Set, |
||
12 | Pair, |
||
13 | ValidateArgument, |
||
14 | Exception\LogicException, |
||
15 | Exception\ElementNotFound, |
||
16 | Exception\CannotGroupEmptyStructure, |
||
17 | }; |
||
18 | |||
19 | /** |
||
20 | * @template T |
||
21 | * @template S |
||
22 | */ |
||
23 | final class DoubleIndex implements Implementation |
||
24 | { |
||
25 | private string $keyType; |
||
26 | private string $valueType; |
||
27 | private ValidateArgument $validateKey; |
||
28 | private ValidateArgument $validateValue; |
||
29 | /** @var Sequence\Implementation<T> */ |
||
30 | private Sequence\Implementation $keys; |
||
31 | /** @var Sequence\Implementation<S> */ |
||
32 | private Sequence\Implementation $values; |
||
33 | /** @var Sequence\Implementation<Pair<T, S>> */ |
||
34 | private Sequence\Implementation $pairs; |
||
35 | |||
36 | 84 | public function __construct(string $keyType, string $valueType) |
|
37 | { |
||
38 | 84 | $this->validateKey = Type::of($keyType); |
|
39 | 84 | $this->validateValue = Type::of($valueType); |
|
40 | 84 | $this->keyType = $keyType; |
|
41 | 84 | $this->valueType = $valueType; |
|
42 | 84 | $this->keys = new Sequence\Primitive($keyType); |
|
43 | 84 | $this->values = new Sequence\Primitive($valueType); |
|
44 | /** @var Sequence\Implementation<Pair<T, S>> */ |
||
45 | 84 | $this->pairs = new Sequence\Primitive(Pair::class); |
|
46 | 84 | } |
|
47 | |||
48 | 16 | public function keyType(): string |
|
49 | { |
||
50 | 16 | return $this->keyType; |
|
51 | } |
||
52 | |||
53 | 16 | public function valueType(): string |
|
54 | { |
||
55 | 16 | return $this->valueType; |
|
56 | } |
||
57 | |||
58 | 4 | public function size(): int |
|
59 | { |
||
60 | 4 | return $this->keys->size(); |
|
61 | } |
||
62 | |||
63 | 6 | public function count(): int |
|
64 | { |
||
65 | 6 | return $this->keys->count(); |
|
66 | } |
||
67 | |||
68 | /** |
||
69 | * @param T $key |
||
70 | * @param S $value |
||
71 | * |
||
72 | * @return self<T, S> |
||
73 | */ |
||
74 | 35 | public function put($key, $value): self |
|
75 | { |
||
76 | 35 | ($this->validateKey)($key, 1); |
|
77 | 34 | ($this->validateValue)($value, 2); |
|
78 | |||
79 | 33 | $map = clone $this; |
|
80 | |||
81 | 33 | if ($this->keys->contains($key)) { |
|
82 | 3 | $index = $this->keys->indexOf($key); |
|
83 | 3 | $map->values = $this->values->take($index) |
|
84 | 3 | ->add($value) |
|
85 | 3 | ->append($this->values->drop($index + 1)); |
|
86 | /** @var Sequence\Implementation<Pair<T, S>> */ |
||
87 | 3 | $map->pairs = $this->pairs->take($index) |
|
88 | 3 | ->add(new Pair($key, $value)) |
|
89 | 3 | ->append($this->pairs->drop($index + 1)); |
|
90 | } else { |
||
91 | /** @var Sequence\Implementation<T> */ |
||
92 | 33 | $map->keys = $this->keys->add($key); |
|
93 | 33 | $map->values = $this->values->add($value); |
|
94 | /** @var Sequence\Implementation<Pair<T, S>> */ |
||
95 | 33 | $map->pairs = $this->pairs->add(new Pair($key, $value)); |
|
96 | } |
||
97 | |||
98 | 33 | return $map; |
|
99 | } |
||
100 | |||
101 | /** |
||
102 | * @param T $key |
||
103 | * |
||
104 | * @throws ElementNotFound |
||
105 | * |
||
106 | * @return S |
||
107 | */ |
||
108 | 18 | public function get($key) |
|
109 | { |
||
110 | 18 | if (!$this->keys->contains($key)) { |
|
111 | 1 | throw new ElementNotFound($key); |
|
112 | } |
||
113 | |||
114 | 17 | return $this->values->get( |
|
115 | 17 | $this->keys->indexOf($key) |
|
116 | ); |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * @param T $key |
||
121 | */ |
||
122 | 3 | public function contains($key): bool |
|
123 | { |
||
124 | 3 | return $this->keys->contains($key); |
|
125 | } |
||
126 | |||
127 | /** |
||
128 | * @return self<T, S> |
||
129 | */ |
||
130 | 5 | public function clear(): self |
|
131 | { |
||
132 | 5 | $map = clone $this; |
|
133 | 5 | $map->keys = $this->keys->clear(); |
|
134 | 5 | $map->values = $this->values->clear(); |
|
135 | 5 | $map->pairs = $this->pairs->clear(); |
|
136 | |||
137 | 5 | return $map; |
|
138 | } |
||
139 | |||
140 | /** |
||
141 | * @param Implementation<T, S> $map |
||
142 | */ |
||
143 | 2 | public function equals(Implementation $map): bool |
|
144 | { |
||
145 | 2 | if (!$map->keys()->equals($this->keys())) { |
|
146 | 1 | return false; |
|
147 | } |
||
148 | |||
149 | 2 | foreach ($this->pairs->iterator() as $pair) { |
|
150 | 2 | if ($map->get($pair->key()) !== $pair->value()) { |
|
151 | 2 | return false; |
|
152 | } |
||
153 | } |
||
154 | |||
155 | 1 | return true; |
|
156 | } |
||
157 | |||
158 | /** |
||
159 | * @param callable(T, S): bool $predicate |
||
160 | * |
||
161 | * @return self<T, S> |
||
162 | */ |
||
163 | 1 | public function filter(callable $predicate): self |
|
164 | { |
||
165 | 1 | $map = $this->clear(); |
|
166 | |||
167 | 1 | foreach ($this->pairs->iterator() as $pair) { |
|
168 | 1 | if ($predicate($pair->key(), $pair->value()) === true) { |
|
169 | /** @psalm-suppress MixedArgumentTypeCoercion */ |
||
170 | 1 | $map->keys = $map->keys->add($pair->key()); |
|
171 | /** @psalm-suppress MixedArgumentTypeCoercion */ |
||
172 | 1 | $map->values = $map->values->add($pair->value()); |
|
173 | /** |
||
174 | * @psalm-suppress MixedArgumentTypeCoercion |
||
175 | * @var Sequence\Implementation<Pair<T, S>> |
||
176 | */ |
||
177 | 1 | $map->pairs = $map->pairs->add($pair); |
|
178 | } |
||
179 | } |
||
180 | |||
181 | 1 | return $map; |
|
182 | } |
||
183 | |||
184 | /** |
||
185 | * @param callable(T, S): void $function |
||
186 | */ |
||
187 | 1 | public function foreach(callable $function): void |
|
188 | { |
||
189 | 1 | foreach ($this->pairs->iterator() as $pair) { |
|
190 | 1 | $function($pair->key(), $pair->value()); |
|
191 | } |
||
192 | 1 | } |
|
193 | |||
194 | /** |
||
195 | * @template D |
||
196 | * @param callable(T, S): D $discriminator |
||
197 | * |
||
198 | * @throws CannotGroupEmptyStructure |
||
199 | * |
||
200 | * @return Map<D, Map<T, S>> |
||
201 | */ |
||
202 | 2 | public function groupBy(callable $discriminator): Map |
|
203 | { |
||
204 | 2 | if ($this->empty()) { |
|
205 | 1 | throw new CannotGroupEmptyStructure; |
|
206 | } |
||
207 | |||
208 | 1 | $groups = null; |
|
209 | |||
210 | 1 | foreach ($this->pairs->iterator() as $pair) { |
|
211 | 1 | $key = $discriminator($pair->key(), $pair->value()); |
|
212 | |||
213 | 1 | if ($groups === null) { |
|
214 | /** @var Map<D, Map<T, S>> */ |
||
215 | 1 | $groups = Map::of( |
|
216 | 1 | Type::determine($key), |
|
217 | 1 | Map::class, |
|
218 | ); |
||
219 | } |
||
220 | |||
221 | 1 | if ($groups->contains($key)) { |
|
222 | /** @var Map<T, S> */ |
||
223 | 1 | $group = $groups->get($key); |
|
224 | /** @var Map<T, S> */ |
||
225 | 1 | $group = ($group)($pair->key(), $pair->value()); |
|
226 | |||
227 | 1 | $groups = ($groups)($key, $group); |
|
228 | } else { |
||
229 | /** @var Map<T, S> */ |
||
230 | 1 | $group = $this->clearMap()($pair->key(), $pair->value()); |
|
231 | |||
232 | 1 | $groups = ($groups)($key, $group); |
|
233 | } |
||
234 | } |
||
235 | |||
236 | /** @var Map<D, Map<T, S>> */ |
||
237 | 1 | return $groups; |
|
238 | } |
||
239 | |||
240 | /** |
||
241 | * @return Set<T> |
||
242 | */ |
||
243 | 10 | public function keys(): Set |
|
244 | { |
||
245 | 10 | return $this->keys->toSetOf($this->keyType); |
|
246 | } |
||
247 | |||
248 | /** |
||
249 | * @return Sequence<S> |
||
250 | */ |
||
251 | 5 | public function values(): Sequence |
|
252 | { |
||
253 | 5 | return $this->values->toSequenceOf($this->valueType); |
|
254 | } |
||
255 | |||
256 | /** |
||
257 | * @param callable(T, S): (S|Pair<T, S>) $function |
||
258 | * |
||
259 | * @return self<T, S> |
||
260 | */ |
||
261 | 3 | public function map(callable $function): self |
|
262 | { |
||
263 | 3 | $map = $this->clear(); |
|
264 | |||
265 | 3 | foreach ($this->pairs->iterator() as $pair) { |
|
266 | 3 | $return = $function($pair->key(), $pair->value()); |
|
267 | |||
268 | 3 | if ($return instanceof Pair) { |
|
269 | /** @var T */ |
||
270 | 2 | $key = $return->key(); |
|
271 | /** @var S */ |
||
272 | 2 | $value = $return->value(); |
|
273 | } else { |
||
274 | 2 | $key = $pair->key(); |
|
275 | 2 | $value = $return; |
|
276 | } |
||
277 | |||
278 | 3 | $map = $map->put($key, $value); |
|
279 | } |
||
280 | |||
281 | 1 | return $map; |
|
282 | } |
||
283 | |||
284 | /** |
||
285 | * @param T $key |
||
286 | * |
||
287 | * @return self<T, S> |
||
288 | */ |
||
289 | 1 | public function remove($key): Implementation |
|
290 | { |
||
291 | 1 | if (!$this->contains($key)) { |
|
292 | 1 | return $this; |
|
293 | } |
||
294 | |||
295 | 1 | $index = $this->keys->indexOf($key); |
|
296 | 1 | $map = clone $this; |
|
297 | 1 | $map->keys = $this |
|
298 | 1 | ->keys |
|
299 | 1 | ->slice(0, $index) |
|
300 | 1 | ->append($this->keys->slice($index + 1, $this->keys->size())); |
|
301 | 1 | $map->values = $this |
|
302 | 1 | ->values |
|
303 | 1 | ->slice(0, $index) |
|
304 | 1 | ->append($this->values->slice($index + 1, $this->values->size())); |
|
305 | /** @var Sequence\Implementation<Pair<T, S>> */ |
||
306 | 1 | $map->pairs = $this |
|
307 | 1 | ->pairs |
|
308 | 1 | ->slice(0, $index) |
|
309 | 1 | ->append($this->pairs->slice($index + 1, $this->pairs->size())); |
|
310 | |||
311 | 1 | return $map; |
|
312 | } |
||
313 | |||
314 | /** |
||
315 | * @param Implementation<T, S> $map |
||
316 | * |
||
317 | * @return self<T, S> |
||
318 | */ |
||
319 | 1 | public function merge(Implementation $map): self |
|
320 | { |
||
321 | 1 | return $map->reduce( |
|
322 | 1 | $this, |
|
323 | function(self $carry, $key, $value): self { |
||
324 | 1 | return $carry->put($key, $value); |
|
325 | 1 | } |
|
326 | ); |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * @param callable(T, S): bool $predicate |
||
331 | * |
||
332 | * @return Map<bool, Map<T, S>> |
||
333 | */ |
||
334 | 1 | public function partition(callable $predicate): Map |
|
335 | { |
||
336 | 1 | $truthy = $this->clearMap(); |
|
337 | 1 | $falsy = $this->clearMap(); |
|
338 | |||
339 | 1 | foreach ($this->pairs->iterator() as $pair) { |
|
340 | 1 | $return = $predicate($pair->key(), $pair->value()); |
|
341 | |||
342 | 1 | if ($return === true) { |
|
343 | 1 | $truthy = ($truthy)($pair->key(), $pair->value()); |
|
344 | } else { |
||
345 | 1 | $falsy = ($falsy)($pair->key(), $pair->value()); |
|
346 | } |
||
347 | } |
||
348 | |||
349 | /** |
||
350 | * @psalm-suppress InvalidScalarArgument |
||
351 | * @psalm-suppress InvalidArgument |
||
352 | */ |
||
353 | 1 | return Map::of('bool', Map::class) |
|
354 | 1 | (true, $truthy) |
|
355 | 1 | (false, $falsy); |
|
356 | } |
||
357 | |||
358 | /** |
||
359 | * @template R |
||
360 | * @param R $carry |
||
361 | * @param callable(R, T, S): R $reducer |
||
362 | * |
||
363 | * @return R |
||
364 | */ |
||
365 | 2 | public function reduce($carry, callable $reducer) |
|
366 | { |
||
367 | 2 | foreach ($this->pairs->iterator() as $pair) { |
|
368 | 2 | $carry = $reducer($carry, $pair->key(), $pair->value()); |
|
369 | } |
||
370 | |||
371 | 2 | return $carry; |
|
372 | } |
||
373 | |||
374 | 3 | public function empty(): bool |
|
375 | { |
||
376 | 3 | return $this->pairs->empty(); |
|
377 | } |
||
378 | |||
379 | /** |
||
380 | * @template ST |
||
381 | * |
||
382 | * @param callable(T, S): \Generator<ST> $mapper |
||
383 | * |
||
384 | * @return Sequence<ST> |
||
385 | */ |
||
386 | 1 | public function toSequenceOf(string $type, callable $mapper): Sequence |
|
387 | { |
||
388 | /** @var Sequence<ST> */ |
||
389 | 1 | $sequence = Sequence::of($type); |
|
390 | |||
391 | 1 | foreach ($this->pairs->iterator() as $pair) { |
|
392 | 1 | foreach ($mapper($pair->key(), $pair->value()) as $newValue) { |
|
393 | 1 | $sequence = ($sequence)($newValue); |
|
394 | } |
||
395 | } |
||
396 | |||
397 | 1 | return $sequence; |
|
398 | } |
||
399 | |||
400 | /** |
||
401 | * @template ST |
||
402 | * |
||
403 | * @param callable(T, S): \Generator<ST> $mapper |
||
404 | * |
||
405 | * @return Set<ST> |
||
406 | */ |
||
407 | 1 | public function toSetOf(string $type, callable $mapper): Set |
|
408 | { |
||
409 | /** @var Set<ST> */ |
||
410 | 1 | $set = Set::of($type); |
|
411 | |||
412 | 1 | foreach ($this->pairs->iterator() as $pair) { |
|
413 | 1 | foreach ($mapper($pair->key(), $pair->value()) as $newValue) { |
|
414 | 1 | $set = ($set)($newValue); |
|
415 | } |
||
416 | } |
||
417 | |||
418 | 1 | return $set; |
|
419 | } |
||
420 | |||
421 | /** |
||
422 | * @template MT |
||
423 | * @template MS |
||
424 | * |
||
425 | * @param null|callable(T, S): \Generator<MT, MS> $mapper |
||
426 | * |
||
427 | * @return Map<MT, MS> |
||
428 | */ |
||
429 | 1 | public function toMapOf(string $key, string $value, callable $mapper = null): Map |
|
430 | { |
||
431 | /** @psalm-suppress MissingParamType */ |
||
432 | 1 | $mapper ??= static fn($k, $v): \Generator => yield $k => $v; |
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
433 | |||
434 | /** @var Map<MT, MS> */ |
||
435 | 1 | $map = Map::of($key, $value); |
|
436 | |||
437 | 1 | foreach ($this->pairs->iterator() as $pair) { |
|
438 | 1 | foreach ($mapper($pair->key(), $pair->value()) as $newKey => $newValue) { |
|
439 | 1 | $map = ($map)($newKey, $newValue); |
|
440 | } |
||
441 | } |
||
442 | |||
443 | 1 | return $map; |
|
444 | } |
||
445 | |||
446 | /** |
||
447 | * @return Map<T, S> |
||
448 | */ |
||
449 | 2 | private function clearMap(): Map |
|
450 | { |
||
451 | 2 | return Map::of($this->keyType, $this->valueType); |
|
452 | } |
||
453 | } |
||
454 |