Completed
Pull Request — master (#5)
by
unknown
03:43
created

SimpleArrayLibrary::sortArrayKeysByArray()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 2
Metric Value
c 2
b 1
f 2
dl 0
loc 17
rs 8.8571
cc 6
eloc 11
nc 5
nop 2
1
<?php
2
3
namespace SimpleArrayLibrary;
4
5
use InvalidArgumentException;
6
use UnexpectedValueException;
7
8
class SimpleArrayLibrary
9
{
10
    /**
11
     * @param array $config
12
     * @param array $keys
13
     * @param mixed $value
14
     *
15
     * @return array
16
     */
17
    public static function addConfigRow(array $config, array $keys, $value)
18
    {
19
        // validation
20
        if (self::isAssociative($keys)) {
21
            throw new UnexpectedValueException('Array of config keys must be numeric');
22
        }
23
24
        $row = $value;
25
        for ($i = count($keys) - 1; $i >= 0; $i--) {
26
            $row = [$keys[$i] => $row];
27
        }
28
        $config = self::insertSubArray($config, $row);
29
30
        return $config;
31
    }
32
33
    /**
34
     * Checks if all elements of the array have same value
35
     *
36
     * @param array $haystack
37
     * @param mixed $needle
38
     *
39
     * @return boolean
40
     */
41
    public static function allElementsEqual(array $haystack, $needle = null)
42
    {
43
        $return = true;
44
        // if both arguments have been passed, use value argument (regardless of whether it is null or not
45
        if (func_num_args() == 2) {
46
            $compareAgainst = $needle;
47
        } // only one argument has been passed, so compare elements only to each other
48
        else {
49
            $compareAgainst = reset($haystack);
50
        }
51
        foreach ($haystack as $element) {
52
            if ($compareAgainst != $element) {
53
                $return = false;
54
                break;
55
            }
56
        }
57
58
        return $return;
59
    }
60
61
    const TYPE_INT = 'int';
62
    const TYPE_STRING = 'string';
63
    const TYPE_FLOAT = 'float';
64
    const TYPE_BOOL = 'bool';
65
    const TYPE_ARRAY = 'array';
66
    const TYPE_OBJECT = 'object';
67
68
    /**
69
     * @param array $matrix
70
     * @param array $castMap
71
     * @param bool  $allKeysMustBePresent
72
     *
73
     * @return array
74
     */
75
    public static function castColumns(array $matrix, array $castMap, $allKeysMustBePresent = true)
76
    {
77
        if (!is_bool($allKeysMustBePresent)) {
78
            throw new InvalidArgumentException('Third parameter must be a boolean');
79
        }
80
        if (empty($matrix)) {
81
            return $matrix;
82
        }
83
        if (self::countMinDepth($matrix) < 2) {
84
            throw new UnexpectedValueException('Can not cast columns on one dimensional array');
85
        }
86
87
        foreach ($matrix as $key => $row) {
88
            foreach ($castMap as $column => $type) {
89
                if (isset($row[$column]) || array_key_exists($column, $row)) {
90
                    switch ($type) {
91
                        case self::TYPE_INT:
92
                            $matrix[$key][$column] = (int)$row[$column];
93
                            break;
94
                        case self::TYPE_STRING:
95
                            $matrix[$key][$column] = (string)$row[$column];
96
                            break;
97
                        case self::TYPE_FLOAT:
98
                            $matrix[$key][$column] = (float)$row[$column];
99
                            break;
100
                        case self::TYPE_BOOL:
101
                            $matrix[$key][$column] = (bool)$row[$column];
102
                            break;
103
                        case self::TYPE_ARRAY:
104
                            $matrix[$key][$column] = (array)$row[$column];
105
                            break;
106
                        case self::TYPE_OBJECT:
107
                            $matrix[$key][$column] = (object)$row[$column];
108
                            break;
109
                        default:
110
                            throw new UnexpectedValueException('Invalid type: ' . $type);
111
                    }
112
                } elseif ($allKeysMustBePresent) {
113
                    throw new UnexpectedValueException('Column: ' . $column . ' missing in row: ' . $key);
114
                }
115
            }
116
        }
117
118
        return $matrix;
119
    }
120
121
    /**
122
     * Counts maximum array depth recursively
123
     *
124
     * @param array $array
125
     *
126
     * @return int
127
     */
128
    public static function countMaxDepth(array $array)
129
    {
130
        $maxDepth = 1;
131
        foreach ($array as $element) {
132
            $depth = 1;
133
            if (is_array($element)) {
134
                $depth += self::countMaxDepth($element);
135
            }
136
            if ($depth > $maxDepth) $maxDepth = $depth;
137
        }
138
139
        return $maxDepth;
140
    }
141
142
    /**
143
     * Counts maximum array depth iteratively
144
     *
145
     * @param array $array
146
     *
147
     * @return int
148
     */
149
    public static function countMaxDepthIterative(array $array)
150
    {
151
        $copy     = $array;
152
        $maxDepth = 1;
153
154
        foreach ($copy as $element) {
155
            $depth = 1;
156
            while (!empty($element)) {
157
                if (is_array($element)) {
158
                    ++$depth;
159
                    $tmp = array_shift($element);
160
                    if (is_array($tmp)) {
161
                        array_push($element, array_shift($tmp));
162
                    }
163
                } else {
164
                    break;
165
                }
166
            }
167
            if ($depth > $maxDepth) {
168
                $maxDepth = $depth;
169
            }
170
        }
171
172
        return $maxDepth;
173
    }
174
175
    /**
176
     * Counts minimum array depth
177
     *
178
     * @param mixed $potentialArray
179
     * @param int   $depth
180
     *
181
     * @return int
182
     * @throws InvalidArgumentException
183
     */
184
    public static function countMinDepth($potentialArray, $depth = 0)
185
    {
186
        // validation, must be positive int or 0
187
        if (!self::isLogicallyCastableToInt($depth)) {
188
            throw new InvalidArgumentException('Depth parameter must be non-negative integer');
189
        }
190
        if ($depth < 0) {
191
            throw new InvalidArgumentException('Depth parameter must be non-negative integer');
192
        }
193
194
        $return = $depth;
195
        if (is_array($potentialArray)) {
196
            $return++;
197
            $childrenDepths = array();
198
            foreach ($potentialArray as $element) {
199
                $childrenDepths[] = self::countMinDepth($element, $return);
200
            }
201
            $return = empty($childrenDepths) ? $return : min($childrenDepths);
202
        }
203
204
        return $return;
205
    }
206
207
    /**
208
     * @param array $matrix
209
     * @param mixed $columns
210
     *
211
     * @return array
212
     */
213
    public static function deleteColumns(array $matrix, array $columns)
214
    {
215
        // validate input
216
        if (self::countMinDepth($matrix) < 2) {
217
            throw new UnexpectedValueException('Can not delete columns on one dimensional array');
218
        }
219
        if (self::countMinDepth($columns) != 1) {
220
            throw new InvalidArgumentException('Invalid column');
221
        }
222
223
        // remove columns in every row
224
        foreach ($matrix as $key => $row) {
225
            foreach ($columns as $column) {
226
                unset($matrix[$key][$column]);
227
            }
228
        }
229
230
        return $matrix;
231
    }
232
233
    /**
234
     * Extracts a column from an array
235
     *
236
     * @param array $array
237
     * @param array $columns
238
     * @param bool  $allRowsMustHaveAllColumns
239
     *
240
     * @return array
241
     * @throws UnexpectedValueException
242
     */
243
    public static function getColumns(array $array, array $columns, $allRowsMustHaveAllColumns = false)
244
    {
245
        // validation
246
        foreach ($array as $key => $row) {
247
            if (!is_array($row)) {
248
                throw new UnexpectedValueException('Array element "' . $key . '" is not an array');
249
            }
250
        }
251
        foreach ($columns as $key => $column) {
252
            if (!is_string($column) && !is_numeric($column)) {
253
                throw new InvalidArgumentException('Invalid column type in columns array, index "' . $key . '"');
254
            }
255
        }
256
        if (!is_bool($allRowsMustHaveAllColumns)) {
257
            throw new InvalidArgumentException('allRowsMustHaveAllColumns flag must be boolean');
258
        }
259
260
        $return = array_fill_keys($columns, array());
261
        foreach ($array as $key => $row) {
262
            foreach ($columns as $column) {
263
                if (isset($row[$column]) || array_key_exists($column, $row)) {
264
                    $return[$column][$key] = $row[$column];
265
                } elseif ($allRowsMustHaveAllColumns) {
266
                    throw new UnexpectedValueException('Row "' . $key . '" is missing column: "' . $column . '"');
267
                }
268
            }
269
        }
270
271
        return $return;
272
    }
273
274
    /**
275
     * Checks if an array is rectangular array and returns dimensions or -1 if it's not rectangular
276
     *
277
     * @param array $array
278
     *
279
     * @return int|array
280
     */
281
    public static function getRectangularDimensions(array $array)
282
    {
283
        $return = -1;
284
        $allArrays = array_map('is_array', $array);
285
        // all elements are arrays, iterate through them and call the static function recursively
286
        if (self::allElementsEqual($allArrays, true)) {
287
            $elementsPerArray = array();
288
            foreach ($array as $row) {
289
                $noElements = self::getRectangularDimensions($row);
290
                if ($noElements == -1) {
291
                    return $noElements;
292
                }
293
                $elementsPerArray[] = $noElements;
294
            }
295
            if (!self::allElementsEqual($elementsPerArray)) {
296
                return -1;
297
            } else {
298
                $return   = reset($elementsPerArray);
299
                $return[] = count($elementsPerArray);
300
            }
301
        } // none of the elements are arrays, return number of elements of the "bottom" array
302
        elseif (self::allElementsEqual($allArrays, false)) {
303
            $return = array(0 => count($array));
304
        }
305
306
        return $return;
307
    }
308
309
    /**
310
     * Checks if $array's keys contain all of $subArray's values
311
     *
312
     * @param array $haystack
313
     * @param array $needles
314
     *
315
     * @return bool
316
     */
317
    public static function hasAllKeys(array $haystack, array $needles)
318
    {
319
        return self::hasAllValues(array_keys($haystack), $needles);
320
    }
321
322
    /**
323
     * Checks if $array's keys contain all of $subArray's values
324
     *
325
     * @param array $haystack
326
     * @param array $needles
327
     *
328
     * @return bool
329
     */
330
    public static function hasAllValues(array $haystack, array $needles)
331
    {
332
        return array_diff($needles, $haystack) ? false : true;
333
    }
334
335
    /**
336
     * Checks if $array's keys contain all of $subArray's values
337
     *
338
     * @param array $haystack
339
     * @param array $needles
340
     *
341
     * @return bool
342
     */
343
    public static function hasAllValuesMultiDimensional(array $haystack, array $needles)
344
    {
345
        $return = true;
346
        foreach ($needles as $needle) {
347
            if (!in_array($needle, $haystack)) {
348
                $return = false;
349
                break;
350
            }
351
        }
352
353
        return $return;
354
    }
355
356
    /**
357
     * Checks whether array has only provided keys as indexes
358
     *
359
     * @param array $haystack
360
     * @param array $needles
361
     *
362
     * @return bool
363
     */
364
    public static function hasOnlyKeys(array $haystack, array $needles)
365
    {
366
        return self::haveSameKeys($haystack, array_flip($needles));
367
    }
368
369
    /**
370
     * Checks if two arrays have all equal keys
371
     *
372
     * @param array $array1
373
     * @param array $array2
374
     *
375
     * @return boolean
376
     */
377
    public static function haveSameKeys(array $array1, array $array2)
378
    {
379
        return self::hasAllKeys($array1, array_keys($array2)) && self::hasAllKeys($array2, array_keys($array1)) ? true : false;
380
    }
381
382
    /**
383
     * Check if two arrays have all equal values
384
     *
385
     * @param array $array1
386
     * @param array $array2
387
     *
388
     * @return bool
389
     */
390
    public static function haveSameValues($array1, $array2)
391
    {
392
        return self::hasAllValues($array1, $array2) && self::hasAllValues($array2, $array1) ? true : false;
393
    }
394
395
    /**
396
     * @param mixed $array
397
     * @param mixed $subArray
398
     * @param bool  $overwrite
399
     * @param bool  $ignoreIfExists
400
     *
401
     * @return array
402
     */
403
    public static function insertSubArray($array, $subArray, $overwrite = false, $ignoreIfExists = false)
404
    {
405
        // validate
406
        if (!is_bool($overwrite)) {
407
            throw new InvalidArgumentException('Overwrite indicator must be a boolean');
408
        }
409
        if (!is_bool($ignoreIfExists)) {
410
            throw new InvalidArgumentException('Ignore if exists indicator must be a boolean');
411
        }
412
413
        if (!is_array($subArray) || !is_array($array)) {
414
            // $subArray[$key] is leaf of the array
415
            if ($overwrite) {
416
                $array = $subArray;
417
            } elseif (!$ignoreIfExists) {
418
                throw new UnexpectedValueException('Sub-array already exists');
419
            }
420
        } else {
421
            $key = key($subArray);
422
            if (isset($array[$key]) || array_key_exists($key, $array)) {
423
                $array[$key] = self::insertSubArray($array[$key], $subArray[$key], $overwrite, $ignoreIfExists);
424
            } else {
425
                $array[$key] = $subArray[$key];
426
            }
427
        }
428
429
        return $array;
430
    }
431
432
    /**
433
     * Checks whether array is associative
434
     *
435
     * @param array $array
436
     *
437
     * @return bool
438
     */
439
    public static function isAssociative(array $array)
440
    {
441
        return (bool)count(array_filter(array_keys($array), 'is_string'));
442
    }
443
444
    /**
445
     * Checks whether array is numeric
446
     *
447
     * @param array $array
448
     *
449
     * @return bool
450
     */
451
    public static function isNumeric(array $array)
452
    {
453
        return array_keys($array) == range(0, count($array) - 1);
454
    }
455
456
    /**
457
     * @param mixed $input1
458
     * @param mixed $input2
459
     *
460
     * @return bool
461
     */
462
    public static function isStructureSame($input1, $input2)
463
    {
464
        $return = true;
465
        if (is_array($input1) && is_array($input2)) {
466
            if (!self::compareArrays($input1, $input2) || !self::compareArrays($input2, $input1)) {
467
                $return = false;
468
            }
469
        } else {
470
            $return = !is_array($input1) && !is_array($input2);
471
        }
472
473
        return $return;
474
    }
475
476
    /**
477
     * @param array $input1
478
     * @param array $input2
479
     *
480
     * @return bool
481
     */
482
    private static function compareArrays(array $input1, array $input2)
483
    {
484
        foreach ($input1 as $key => $value) {
485
            if (!array_key_exists($key, $input2)) {
486
                return false;
487
            } else {
488
                if (!self::isStructureSame($value, $input2[$key])) {
489
                    return false;
490
                }
491
            }
492
        }
493
494
        return true;
495
    }
496
497
    /**
498
     * Checks whether $subArray is contained in $array
499
     *
500
     * @param array $array
501
     * @param array $subArray
502
     * @param bool  $strictComparison
503
     *
504
     * @return bool
505
     * @throws InvalidArgumentException
506
     */
507
    public static function isSubArray(array $array, array $subArray, $strictComparison = true)
508
    {
509
        if (!is_bool($strictComparison)) {
510
            throw new InvalidArgumentException('Strict comparison parameter must be a boolean');
511
        }
512
513
        $return = true;
514
        foreach ($subArray as $key => $value) {
515
            if (isset($array[$key]) || array_key_exists($key, $array)) {
516
                $check = $strictComparison ? $array[$key] !== $subArray[$key] : $array[$key] != $subArray[$key];
517
                if ($check) {
518
                    $return = false;
519
                    break;
520
                }
521
            } else {
522
                $return = false;
523
                break;
524
            }
525
        }
526
527
        return $return;
528
    }
529
530
    /**
531
     * Selects random sub array
532
     *
533
     * @param array $array
534
     * @param int $numberOfRequiredElements
535
     *
536
     * @return array
537
     * @throws InvalidArgumentException
538
     */
539
    public static function selectRandomArrayElements(array $array, $numberOfRequiredElements)
540
    {
541
        // validation, must be positive int or 0
542
        if (!self::isLogicallyCastableToInt($numberOfRequiredElements)) {
543
            throw new InvalidArgumentException('Number of requested elements parameter must be a positive integer');
544
        }
545
        if ($numberOfRequiredElements <= 0) {
546
            throw new InvalidArgumentException('Number of requested elements parameter must be a positive integer');
547
        }
548
549
        $selected = $array;
550
        if (count($array) > $numberOfRequiredElements) {
551
            // select required number of random keys
552
            $selectedKeys = array_rand($array, $numberOfRequiredElements);
553
            $selectedKeys = $numberOfRequiredElements == 1 ? [$selectedKeys] : $selectedKeys;
554
            // select only array members with selected random keys
555
            $selected = array_intersect_key($array, array_fill_keys($selectedKeys, null));
556
        }
557
558
        return $selected;
559
    }
560
561
    /**
562
     * @param array $matrix
563
     * @param mixed $column
564
     * @param mixed $value
565
     * @param bool  $insertIfMissing
566
     * @param bool  $overwrite
567
     *
568
     * @return array
569
     */
570
    public static function setColumn(array $matrix, $column, $value, $insertIfMissing = true, $overwrite = true)
571
    {
572
        // validate input
573
        if (self::countMinDepth($matrix) < 2) {
574
            throw new UnexpectedValueException('Can not set columns on one dimensional array');
575
        }
576
        if (!is_scalar($column)) {
577
            throw new InvalidArgumentException('Invalid column');
578
        }
579
        if (!is_bool($insertIfMissing)) {
580
            throw new InvalidArgumentException('Insert if missing indicator must be a boolean');
581
        }
582
        if (!is_bool($overwrite)) {
583
            throw new InvalidArgumentException('Overwrite indicator must be a boolean');
584
        }
585
586
        foreach ($matrix as $key => $row) {
587
            if (isset($row[$column]) || array_key_exists($column, $row)) {
588
                $matrix[$key][$column] = ($overwrite ? $value : $matrix[$key][$column]);
589
            } elseif ($insertIfMissing) {
590
                $matrix[$key][$column] = $value;
591
            }
592
        }
593
594
        return $matrix;
595
    }
596
597
    /**
598
     * Check whether casting a variable to int would conway useful information
599
     *
600
     * @param mixed $input
601
     *
602
     * @return bool
603
     */
604
    private static function isLogicallyCastableToInt($input)
605
    {
606
        return !is_bool($input) && filter_var($input, FILTER_VALIDATE_INT) !== false;
607
    }
608
609
    /**
610
     * Sort first array by values from the second array.
611
     * Both arrays must be one dimensional.
612
     *
613
     * @param array $arrayToSort
614
     * @param array $orderArray
615
     *
616
     * @return array
617
     */
618
    public static function sortArrayByArray(array $arrayToSort, array $orderArray)
619
    {
620
        // validate input
621
        if (self::countMinDepth($arrayToSort) != 1 || self::countMinDepth($orderArray) != 1) {
622
            throw new UnexpectedValueException('Both array to sort and order array must be of one dimensional arrays');
623
        }
624
625
        $arrayToSort = array_values($arrayToSort);
626
        $ordered = array();
627
        foreach ($orderArray as $key => $value) {
628
            if (in_array($value, $arrayToSort)) {
629
                $ordered[] = $value;
630
                $keyToUnset = array_search($value, $arrayToSort);
631
                unset($arrayToSort[$keyToUnset]);
632
            }
633
        }
634
        return array_merge($ordered, $arrayToSort);
635
    }
636
637
    /**
638
     * Sort first array by keys, by comparing them to the values from the second array.
639
     *
640
     * @param array $arrayToSort
641
     * @param array $orderArray
642
     *
643
     * @return array
644
     */
645
    public static function sortArrayKeysByArray(array $arrayToSort, array $orderArray)
646
    {
647
        if (!self::isAssociative($arrayToSort)) {
648
            throw new UnexpectedValueException('Array to sort must be associative');
649
        }
650
        if (self::countMinDepth($orderArray) != 1) {
651
            throw new UnexpectedValueException('Ordering array must be one dimensional');
652
        }
653
        $ordered = array();
654
        foreach ($orderArray as $value) {
655
            if (in_array($value, array_keys($arrayToSort)) && isset($arrayToSort[$value])) {
656
                $ordered[$value] = $arrayToSort[$value];
657
                unset($arrayToSort[$value]);
658
            }
659
        }
660
        return $ordered + $arrayToSort;
661
    }
662
}