Passed
Push — master ( 6e3e92...9760cf )
by Eric
02:58 queued 55s
created

Arrays::interlace()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 28
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 13
nc 10
nop 1
dl 0
loc 28
ccs 14
cts 14
cp 1
crap 7
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of Esi\Utility.
7
 *
8
 * (c) 2017 - 2025 Eric Sizemore <[email protected]>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE.md file that was distributed with this source code.
12
 */
13
14
namespace Esi\Utility;
15
16
use ArrayAccess;
17
use WeakMap;
18
19
/**
20
 * Array utilities.
21
 */
22
abstract class Arrays
23
{
24
    /**
25
     * flatten().
26
     *
27
     * Flattens a multidimensional array.
28
     *
29
     * Keys are preserved based on $separator.
30
     *
31
     * @since 1.2.0
32
     *
33
     * @template TValue
34
     *
35
     * @param array<array-key, array<array-key, TValue>|TValue> $array
1 ignored issue
show
Documentation Bug introduced by
The doc comment array<array-key, array<array-key, TValue>|TValue> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, array<array-key, TValue>|TValue>.
Loading history...
36
     *
37
     * @return array<string, TValue>
38
     */
39 3
    public static function flatten(array $array, string $separator = '.', string $prepend = ''): array
40
    {
41
        /** @var array<string, TValue> $result */
42 3
        $result = [];
43
44 3
        foreach ($array as $key => $value) {
45 2
            $currentKey = $prepend . $key;
46
47 2
            if (\is_array($value)) {
48 2
                $flattened = self::flatten($value, $separator, $currentKey . $separator);
49 2
                $result    = array_merge($result, $flattened);
50 2
                continue;
51
            }
52
53 2
            $result[$currentKey] = $value;
54
        }
55
56 3
        return $result;
57
    }
58
59
    /**
60
     * get().
61
     *
62
     * Retrieve a value from an array.
63
     *
64
     * @template TKey of array-key
65
     * @template TValue
66
     * @template TDefault
67
     *
68
     * @param array<TKey, TValue>|ArrayAccess<TKey, TValue> $array
69
     * @param TKey                                          $key
70
     * @param TDefault                                      $default
71
     *
72
     * @return TDefault|TValue
73
     */
74 9
    public static function get(array|ArrayAccess $array, int|string $key, mixed $default = null): mixed
75
    {
76 9
        if (self::keyExists($array, $key)) {
77
            /** @var TValue */
78 7
            return $array[$key];
79
        }
80
81
        /** @var TDefault */
82 6
        return $default;
83
    }
84
85
    /**
86
     * Returns an associative array, grouped by $key, where the keys are the distinct values of $key,
87
     * and the values are arrays of items that share the same $key.
88
     *
89
     * *Important to note:* if a $key is provided that does not exist, the result will be an empty array.
90
     *
91
     * @since 2.0.0
92
     *
93
     * @template TKey of array-key
94
     *
95
     * @param array<TKey, array{key?: mixed}> $array
96
     * @param non-empty-string                $key
1 ignored issue
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
97
     *
98
     * @return array<array-key, non-empty-list<array{key?: mixed}>>
1 ignored issue
show
Documentation Bug introduced by
The doc comment array<array-key, non-emp...st<array{key?: mixed}>> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, non-empty-list<array{key?: mixed}>>.
Loading history...
99
     */
100 2
    public static function groupBy(array $array, string $key): array
101
    {
102
        /** @var array<array-key, non-empty-list<array{key?: mixed}>> $result */
103 2
        $result = [];
104
105 2
        foreach ($array as $item) {
106 2
            if (!isset($item[$key])) {
107 1
                continue;
108
            }
109
110
            /** @var array-key */
111 2
            $groupKey = $item[$key];
112
113 2
            if (!isset($result[$groupKey])) {
114 2
                $result[$groupKey] = [$item];
115 2
                continue;
116
            }
117
118 2
            $result[$groupKey][] = $item;
119
        }
120
121 2
        return $result;
122
    }
123
124
    /**
125
     * interlace().
126
     *
127
     * Interlaces one or more arrays' values (not preserving keys).
128
     *
129
     * Example:
130
     * <code>
131
     *      var_dump(Utility\Arrays::interlace(
132
     *          [1, 2, 3],
133
     *          ['a', 'b', 'c']
134
     *      ));
135
     * </code>
136
     *
137
     * Result:
138
     * <code>
139
     *      Array (
140
     *          [0] => 1
141
     *          [1] => a
142
     *          [2] => 2
143
     *          [3] => b
144
     *          [4] => 3
145
     *          [5] => c
146
     *      )
147
     * </code>
148
     *
149
     * @since 1.2.0
150
     *
151
     * @template TValue
152
     *
153
     * @param array<array-key, TValue> ...$arrays
1 ignored issue
show
Documentation Bug introduced by
The doc comment array<array-key, TValue> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, TValue>.
Loading history...
154
     *
155
     * @return array<int, TValue>|false
156
     */
157 4
    public static function interlace(array ...$arrays): array|false
158
    {
159 4
        if ($arrays === []) {
160 1
            return false;
161
        }
162
163 3
        if (\count($arrays) === 1) {
164
            /** @var array<int, TValue> */
165 1
            return array_values($arrays[0]);
166
        }
167
168
        /** @var array<int, TValue> $result */
169 2
        $result    = [];
170 2
        $maxLength = 0;
171
172 2
        foreach ($arrays as $array) {
173 2
            $maxLength = max($maxLength, \count($array));
174
        }
175
176 2
        for ($i = 0; $i < $maxLength; ++$i) {
177 2
            foreach ($arrays as $array) {
178 2
                if (isset($array[$i])) {
179 2
                    $result[] = $array[$i];
180
                }
181
            }
182
        }
183
184 2
        return $result;
185
    }
186
187
    /**
188
     * isAssociative().
189
     *
190
     * Determines if the given array is an associative array.
191
     *
192
     * @param array<array-key, mixed> $array
1 ignored issue
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
193
     */
194 4
    public static function isAssociative(array $array): bool
195
    {
196 4
        if ($array === []) {
197 1
            return false;
198
        }
199
200 3
        return array_keys($array) !== range(0, \count($array) - 1);
201
    }
202
203
    /**
204
     * Checks if a key exists in an array.
205
     *
206
     * @template TKey of array-key
207
     * @template TValue
208
     *
209
     * @param array<TKey, TValue>|ArrayAccess<TKey, TValue> $array
210
     * @param TKey                                          $key
211
     */
212 10
    public static function keyExists(array|ArrayAccess $array, int|string $key): bool
213
    {
214 10
        if ($array instanceof ArrayAccess) {
215 3
            return $array->offsetExists($key);
216
        }
217
218 8
        return \array_key_exists($key, $array);
219
    }
220
221
    /**
222
     * mapDeep().
223
     *
224
     * Recursively applies a callback to all non-iterable elements of an array or an object.
225
     *
226
     * @since 1.2.0
227
     *
228
     * @template TValue
229
     *
230
     * @param mixed                      $data     Data to process
231
     * @param callable(mixed): TValue    $callback Callback to apply to non-iterable values
232
     * @param null|WeakMap<object, true> $seen     Track objects to prevent circular reference issues
233
     */
234 7
    public static function mapDeep(mixed $data, callable $callback, ?WeakMap $seen = null): mixed
235
    {
236
        /** @var WeakMap<object, true> $weakMap */
237 7
        $weakMap = $seen ?? (static function (): WeakMap {
238
            /** @return WeakMap<object, true> */
239 7
            return new WeakMap();
240 7
        })();
241
242 7
        if (!\is_array($data) && !\is_object($data)) {
243 6
            return $callback($data);
244
        }
245
246 6
        if (\is_array($data)) {
247 4
            return array_map(
248 4
                static fn (mixed $item): mixed => self::mapDeep($item, $callback, $weakMap),
249 4
                $data
250 4
            );
251
        }
252
253 5
        if ($weakMap->offsetExists($data)) {
254 2
            return $data;
255
        }
256
257 5
        $weakMap->offsetSet($data, true);
258
259
        try {
260 5
            $props = get_object_vars($data);
261
262 5
            array_walk(
263 5
                $props,
264 5
                static function (mixed $propValue, string $propName) use ($data, $callback, $weakMap): void {
265 4
                    $data->$propName = self::mapDeep($propValue, $callback, $weakMap);
266 5
                }
267 5
            );
268
        } finally {
269 5
            $weakMap->offsetUnset($data);
270
        }
271
272 5
        return $data;
273
    }
274
275
    /**
276
     * set().
277
     *
278
     * Add a value to an array.
279
     *
280
     * @template TKey of array-key
281
     * @template TValue
282
     *
283
     * @param array<TKey, TValue>|ArrayAccess<TKey, TValue> $array
284
     * @param null|TKey                                     $key
285
     * @param TValue                                        $value
286
     *
287
     * @param-out (TValue|array<TKey, TValue>|ArrayAccess<TKey, TValue>) $array
288
     */
289 8
    public static function set(array|ArrayAccess &$array, null|int|string $key, mixed $value): void
290
    {
291 8
        if ($key === null) {
292 1
            $array = $value;
293 1
            return;
294
        }
295
296 7
        if ($array instanceof ArrayAccess) {
297 1
            $array->offsetSet($key, $value);
298 1
            return;
299
        }
300
301 6
        $array[$key] = $value;
302
    }
303
304
    /**
305
     * Checks if a value exists in an array.
306
     *
307
     * @template TKey of array-key
308
     * @template TValue
309
     *
310
     * @param array<TKey, TValue> $array
311
     * @param TValue              $value
312
     */
313 30
    public static function valueExists(array $array, mixed $value): bool
314
    {
315 30
        return \in_array($value, $array, true);
316
    }
317
}
318