GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( fd060d...258593 )
by Baptiste
03:01 queued 10s
created

Primitive::groupBy()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 5

Importance

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