Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
69.28% |
106 / 153 |
|
75.00% |
15 / 20 |
CRAP | |
0.00% |
0 / 1 |
ArrayUtils | |
69.28% |
106 / 153 |
|
75.00% |
15 / 20 |
306.41 | |
0.00% |
0 / 1 |
__construct | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
unsetArray | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
5 | |||
range | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setArray | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
10 | |||
getArray | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
inArrayRecursive | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
7 | |||
anyInArray | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
allInArray | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
arrayToCsv | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
arrayToXml | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getArg | |
44.44% |
4 / 9 |
|
0.00% |
0 / 1 |
9.29 | |||
hasArg | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
arrayFlatten | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
arraySum | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
arraySumRecursive | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
abs | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
power | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
sqrt | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
array_diff_assoc_recursive | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
8 | |||
dot | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
306 | |||
cross3 | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\Utils |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\Utils; |
16 | |
17 | use phpOMS\Math\Matrix\Exception\InvalidDimensionException; |
18 | |
19 | /** |
20 | * Array utils. |
21 | * |
22 | * @package phpOMS\Utils |
23 | * @license OMS License 2.0 |
24 | * @link https://jingga.app |
25 | * @since 1.0.0 |
26 | */ |
27 | final class ArrayUtils |
28 | { |
29 | /** |
30 | * Constructor. |
31 | * |
32 | * @since 1.0.0 |
33 | * @codeCoverageIgnore |
34 | */ |
35 | private function __construct() |
36 | { |
37 | } |
38 | |
39 | /** |
40 | * Check if needle exists in multidimensional array. |
41 | * |
42 | * @param string $path Path to element |
43 | * @param array $data Array |
44 | * @param string $delim Delimiter for path |
45 | * |
46 | * @return array |
47 | * |
48 | * @throws \Exception |
49 | * |
50 | * @since 1.0.0 |
51 | */ |
52 | public static function unsetArray(string $path, array $data, string $delim = '/') : array |
53 | { |
54 | $nodes = \explode($delim, \trim($path, $delim)); |
55 | $prevEl = null; |
56 | $el = &$data; |
57 | $node = null; |
58 | |
59 | if ($nodes === false) { |
60 | throw new \Exception(); // @codeCoverageIgnore |
61 | } |
62 | |
63 | foreach ($nodes as $node) { |
64 | $prevEl = &$el; |
65 | |
66 | if (!isset($el[$node])) { |
67 | break; |
68 | } |
69 | |
70 | $el = &$el[$node]; |
71 | } |
72 | |
73 | if ($prevEl !== null) { |
74 | unset($prevEl[$node]); |
75 | } |
76 | |
77 | return $data; |
78 | } |
79 | |
80 | /** |
81 | * Calculate the range of the array |
82 | * |
83 | * @param int[]|float[] $values Numeric values |
84 | * |
85 | * @return int|float Range of the array |
86 | * |
87 | * @since 1.0.0 |
88 | */ |
89 | public static function range(array $values) : int|float |
90 | { |
91 | return \max($values) - \min($values); |
92 | } |
93 | |
94 | /** |
95 | * Set element in array by path |
96 | * |
97 | * @param string $path Path to element |
98 | * @param array $data Array |
99 | * @param mixed $value Value to add |
100 | * @param string $delim Delimiter for path |
101 | * @param bool $overwrite Overwrite if existing |
102 | * |
103 | * @return array |
104 | * |
105 | * @throws \Exception This exception is thrown if the path is corrupted |
106 | * |
107 | * @since 1.0.0 |
108 | */ |
109 | public static function setArray(string $path, array $data, mixed $value, string $delim = '/', bool $overwrite = false) : array |
110 | { |
111 | $pathParts = \explode($delim, \trim($path, $delim)); |
112 | $current = &$data; |
113 | |
114 | if ($pathParts === false) { |
115 | throw new \Exception(); // @codeCoverageIgnore |
116 | } |
117 | |
118 | foreach ($pathParts as $key) { |
119 | $current = &$current[$key]; |
120 | } |
121 | |
122 | if ($overwrite) { |
123 | $current = $value; |
124 | } elseif (\is_array($current) && !\is_array($value)) { |
125 | $current[] = $value; |
126 | } elseif (\is_array($current) && \is_array($value)) { |
127 | $current = \array_merge($current, $value); |
128 | } elseif (\is_scalar($current) && $current !== null) { |
129 | $current = [$current, $value]; |
130 | } else { |
131 | $current = $value; |
132 | } |
133 | |
134 | return $data; |
135 | } |
136 | |
137 | /** |
138 | * Get element of array by path |
139 | * |
140 | * @param string $path Path to element |
141 | * @param array $data Array |
142 | * @param string $delim Delimiter for path |
143 | * |
144 | * @return mixed |
145 | * |
146 | * @throws \Exception This exception is thrown if the path is corrupted |
147 | * |
148 | * @since 1.0.0 |
149 | */ |
150 | public static function getArray(string $path, array $data, string $delim = '/') : mixed |
151 | { |
152 | $pathParts = \explode($delim, \trim($path, $delim)); |
153 | $current = $data; |
154 | |
155 | if ($pathParts === false) { |
156 | throw new \Exception(); // @codeCoverageIgnore |
157 | } |
158 | |
159 | foreach ($pathParts as $key) { |
160 | if (!isset($current[$key])) { |
161 | return null; |
162 | } |
163 | |
164 | $current = $current[$key]; |
165 | } |
166 | |
167 | return $current; |
168 | } |
169 | |
170 | /** |
171 | * Check if needle exists in multidimensional array. |
172 | * |
173 | * @param mixed $needle Needle for search |
174 | * @param array $haystack Haystack for search |
175 | * @param mixed $key Key that has to match (optional) |
176 | * |
177 | * @return bool |
178 | * |
179 | * @since 1.0.0 |
180 | */ |
181 | public static function inArrayRecursive(mixed $needle, array $haystack, $key = null) : bool |
182 | { |
183 | $found = false; |
184 | |
185 | foreach ($haystack as $k => $item) { |
186 | if ($item === $needle && ($key === null || $key === $k)) { |
187 | return true; |
188 | } elseif (\is_array($item)) { |
189 | $found = self::inArrayRecursive($needle, $item, $key); |
190 | |
191 | if ($found) { |
192 | return true; |
193 | } |
194 | } |
195 | } |
196 | |
197 | return $found; |
198 | } |
199 | |
200 | /** |
201 | * Check if any of the needles are in the array |
202 | * |
203 | * @param array $needles Needles for search |
204 | * @param array $haystack Haystack for search |
205 | * |
206 | * @return bool |
207 | * |
208 | * @since 1.0.0 |
209 | */ |
210 | public static function anyInArray(array $needles, array $haystack) : bool |
211 | { |
212 | foreach ($needles as $needle) { |
213 | if (\in_array($needle, $haystack)) { |
214 | return true; |
215 | } |
216 | } |
217 | |
218 | return false; |
219 | } |
220 | |
221 | /** |
222 | * Check if all of the needles are in the array |
223 | * |
224 | * @param array $needles Needles for search |
225 | * @param array $haystack Haystack for search |
226 | * |
227 | * @return bool |
228 | * |
229 | * @since 1.0.0 |
230 | */ |
231 | public static function allInArray(array $needles, array $haystack) : bool |
232 | { |
233 | foreach ($needles as $needle) { |
234 | if (!\in_array($needle, $haystack)) { |
235 | return false; |
236 | } |
237 | } |
238 | |
239 | return true; |
240 | } |
241 | |
242 | /** |
243 | * Convert array to csv string. |
244 | * |
245 | * @param array $data Data to convert |
246 | * @param string $delimiter Delim to use |
247 | * @param string $enclosure Enclosure to use |
248 | * @param string $escape Escape to use |
249 | * |
250 | * @return string |
251 | * |
252 | * @since 1.0.0 |
253 | */ |
254 | public static function arrayToCsv(array $data, string $delimiter = ';', string $enclosure = '"', string $escape = '\\') : string |
255 | { |
256 | $outstream = \fopen('php://memory', 'r+'); |
257 | |
258 | if ($outstream === false) { |
259 | throw new \Exception(); // @codeCoverageIgnore |
260 | } |
261 | |
262 | foreach ($data as $line) { |
263 | /** @noinspection PhpMethodParametersCountMismatchInspection */ |
264 | \fputcsv($outstream, $line, $delimiter, $enclosure, $escape); |
265 | } |
266 | |
267 | \rewind($outstream); |
268 | $csv = \stream_get_contents($outstream); |
269 | \fclose($outstream); |
270 | |
271 | return $csv === false ? '' : $csv; |
272 | } |
273 | |
274 | /** |
275 | * Convert array to xml string. |
276 | * |
277 | * @param array $data Data to convert |
278 | * @param \SimpleXMLElement $xml XML parent |
279 | * |
280 | * @return string |
281 | * |
282 | * @since 1.0.0 |
283 | */ |
284 | public static function arrayToXml(array $data, \SimpleXMLElement $xml = null) : string |
285 | { |
286 | $xml ??= new \SimpleXMLElement('<root/>'); |
287 | |
288 | foreach ($data as $key => $value) { |
289 | if (\is_array($value)) { |
290 | self::arrayToXml($value, $xml->addChild($key)); |
291 | } else { |
292 | $xml->addChild($key, \htmlspecialchars($value)); |
293 | } |
294 | } |
295 | return (string) $xml->asXML(); |
296 | } |
297 | |
298 | /** |
299 | * Get array value by argument id. |
300 | * |
301 | * Useful for parsing command line parsing |
302 | * |
303 | * @template T |
304 | * |
305 | * @param string $id Id to find |
306 | * @param array<string|int, T> $args CLI command list |
307 | * |
308 | * @return mixed |
309 | * |
310 | * @since 1.0.0 |
311 | */ |
312 | public static function getArg(string $id, array $args) : mixed |
313 | { |
314 | $key = 0; |
315 | if (\is_numeric($id)) { |
316 | $key = ((int) $id) - 1; |
317 | } else { |
318 | if (($key = \array_search($id, $args)) === false || $key === \count($args) - 1) { |
319 | return null; |
320 | } |
321 | |
322 | $key = (int) $key; |
323 | $args = \array_values($args); |
324 | } |
325 | |
326 | $value = $args[$key + 1] ?? null; |
327 | |
328 | return \is_string($value) ? \trim($value, '\'" ') : $value; |
329 | } |
330 | |
331 | /** |
332 | * Check if flag is set |
333 | * |
334 | * @param string $id Id to find |
335 | * @param array<string|int, mixed> $args CLI command list |
336 | * |
337 | * @return int |
338 | * |
339 | * @since 1.0.0 |
340 | */ |
341 | public static function hasArg(string $id, array $args) : int |
342 | { |
343 | return ($key = \array_search($id, $args)) === false |
344 | ? -1 |
345 | : (int) $key; |
346 | } |
347 | |
348 | /** |
349 | * Flatten array |
350 | * |
351 | * Reduces multi dimensional array to one dimensional array. Flatten tries to maintain the index as far as possible. |
352 | * |
353 | * @param array $array Multi dimensional array to flatten |
354 | * |
355 | * @return array |
356 | * |
357 | * @since 1.0.0 |
358 | */ |
359 | public static function arrayFlatten(array $array) : array |
360 | { |
361 | // see collection collapse as alternative?! |
362 | $flat = []; |
363 | $stack = \array_values($array); |
364 | |
365 | while (!empty($stack)) { |
366 | $value = \array_shift($stack); |
367 | |
368 | if (\is_array($value)) { |
369 | $stack = \array_merge(\array_values($value), $stack); |
370 | } else { |
371 | $flat[] = $value; |
372 | } |
373 | } |
374 | |
375 | return $flat; |
376 | } |
377 | |
378 | /** |
379 | * Sum of array elements |
380 | * |
381 | * @param array $array Array to sum |
382 | * @param int $start Start index |
383 | * @param int $count Amount of elements to sum |
384 | * |
385 | * @return int|float |
386 | * |
387 | * @since 1.0.0 |
388 | */ |
389 | public static function arraySum(array $array, int $start = 0, int $count = 0) : int | float |
390 | { |
391 | $count = $count === 0 ? \count($array) : $start + $count; |
392 | $sum = 0; |
393 | $array = \array_values($array); |
394 | |
395 | for ($i = $start; $i <= $count - 1; ++$i) { |
396 | $sum += $array[$i]; |
397 | } |
398 | |
399 | return $sum; |
400 | } |
401 | |
402 | /** |
403 | * Sum multi dimensional array |
404 | * |
405 | * @param array $array Multi dimensional array to flatten |
406 | * |
407 | * @return mixed |
408 | * |
409 | * @since 1.0.0 |
410 | */ |
411 | public static function arraySumRecursive(array $array) : mixed |
412 | { |
413 | return \array_sum(self::arrayFlatten($array)); |
414 | } |
415 | |
416 | /** |
417 | * Applying abs to every array value |
418 | * |
419 | * @param array<int|float> $values Numeric values |
420 | * |
421 | * @return array<int|float> |
422 | * |
423 | * @since 1.0.0 |
424 | */ |
425 | public static function abs(array $values) : array |
426 | { |
427 | $abs = []; |
428 | |
429 | foreach ($values as $value) { |
430 | $abs[] = \abs($value); |
431 | } |
432 | |
433 | return $abs; |
434 | } |
435 | |
436 | /** |
437 | * Power all values in array. |
438 | * |
439 | * @param array<int|float> $values Values to square |
440 | * @param float $exp Exponent |
441 | * |
442 | * @return float[] |
443 | * |
444 | * @since 1.0.0 |
445 | */ |
446 | public static function power(array $values, int | float $exp = 2) : array |
447 | { |
448 | $squared = []; |
449 | |
450 | foreach ($values as $value) { |
451 | $squared[] = $value ** $exp; |
452 | } |
453 | |
454 | return $squared; |
455 | } |
456 | |
457 | /** |
458 | * Sqrt all values in array. |
459 | * |
460 | * @param array<int|float> $values Values to sqrt |
461 | * |
462 | * @return array<int|float> |
463 | * |
464 | * @since 1.0.0 |
465 | */ |
466 | public static function sqrt(array $values) : array |
467 | { |
468 | $squared = []; |
469 | |
470 | foreach ($values as $value) { |
471 | $squared[] = \sqrt($value); |
472 | } |
473 | |
474 | return $squared; |
475 | } |
476 | |
477 | /** |
478 | * Get the associative difference of two arrays. |
479 | * |
480 | * @param array $values1 Array 1 |
481 | * @param array $values2 Array 2 |
482 | * |
483 | * @return array |
484 | * |
485 | * @since 1.0.0 |
486 | */ |
487 | public static function array_diff_assoc_recursive(array $values1, array $values2) : array |
488 | { |
489 | $diff = []; |
490 | foreach ($values1 as $key => $value) { |
491 | if (\is_array($value)) { |
492 | if (!\array_key_exists($key, $values2) || !\is_array($values2[$key])) { |
493 | $diff[$key] = $value; |
494 | } else { |
495 | $subDiff = self::array_diff_assoc_recursive($value, $values2[$key]); |
496 | if (!empty($subDiff)) { |
497 | $diff[$key] = $subDiff; |
498 | } |
499 | } |
500 | } elseif (!\array_key_exists($key, $values2) || $values2[$key] !== $value) { |
501 | $diff[$key] = $value; |
502 | } |
503 | } |
504 | |
505 | return $diff; |
506 | } |
507 | |
508 | /** |
509 | * Get the dot product of two arrays |
510 | * |
511 | * @param array $value1 Value 1 is a matrix or a vector |
512 | * @param array $value2 Value 2 is a matrix or vector (cannot be a matrix if value1 is a vector) |
513 | * |
514 | * @return array |
515 | * |
516 | * @throws InvalidDimensionException |
517 | * @throws \InvalidArgumentException |
518 | * |
519 | * @since 1.0.0 |
520 | */ |
521 | public static function dot(array $value1, array $value2) : int|float|array |
522 | { |
523 | $m1 = \count($value1); |
524 | $n1 = ($isMatrix1 = \is_array($value1[0])) ? \count($value1[0]) : 1; |
525 | |
526 | $m2 = \count($value2); |
527 | $n2 = ($isMatrix2 = \is_array($value2[0])) ? \count($value2[0]) : 1; |
528 | |
529 | $result = null; |
530 | |
531 | if ($isMatrix1 && $isMatrix2) { |
532 | if ($m2 !== $n1) { |
533 | throw new InvalidDimensionException($m2 . 'x' . $n2 . ' not compatible with ' . $m1 . 'x' . $n1); |
534 | } |
535 | |
536 | $result = [[]]; |
537 | for ($i = 0; $i < $m1; ++$i) { // Row of 1 |
538 | for ($c = 0; $c < $n2; ++$c) { // Column of 2 |
539 | $temp = 0; |
540 | |
541 | for ($j = 0; $j < $m2; ++$j) { // Row of 2 |
542 | $temp += $value1[$i][$j] * $value2[$j][$c]; |
543 | } |
544 | |
545 | $result[$i][$c] = $temp; |
546 | } |
547 | } |
548 | } elseif (!$isMatrix1 && !$isMatrix2) { |
549 | if ($m1 !== $m2) { |
550 | throw new InvalidDimensionException($m1 . ' vs. ' . $m2); |
551 | } |
552 | |
553 | $result = 0; |
554 | for ($i = 0; $i < $m1; ++$i) { |
555 | $result += $value1[$i] * $value2[$i]; |
556 | } |
557 | } elseif ($isMatrix1 && !$isMatrix2) { |
558 | $result = []; |
559 | for ($i = 0; $i < $m1; ++$i) { // Row of 1 |
560 | $temp = 0; |
561 | |
562 | for ($c = 0; $c < $m2; ++$c) { // Row of 2 |
563 | $temp += $value1[$i][$c] * $value2[$c]; |
564 | } |
565 | |
566 | $result[$i] = $temp; |
567 | } |
568 | } else { |
569 | throw new \InvalidArgumentException(); |
570 | } |
571 | |
572 | return $result; |
573 | } |
574 | |
575 | /** |
576 | * Calculate the vector corss product |
577 | * |
578 | * @param array $vector1 First 3 vector |
579 | * @param array $vector2 Second 3 vector |
580 | * |
581 | * @return array<int, int|float> |
582 | * |
583 | * @since 1.0.0 |
584 | */ |
585 | public function cross3(array $vector1, array $vector2) : array |
586 | { |
587 | return [ |
588 | $vector1[1] * $vector2[2] - $vector1[2] * $vector2[1], |
589 | $vector1[2] * $vector2[0] - $vector1[0] * $vector2[2], |
590 | $vector1[0] * $vector2[1] - $vector1[1] * $vector2[0], |
591 | ]; |
592 | } |
593 | } |