Completed
Push — master ( b38e1f...e42157 )
by Aleksandar
28:56 queued 02:17
created

SimpleArrayLibrary::allElementsEqual()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 1
Metric Value
c 4
b 0
f 1
dl 0
loc 19
rs 9.2
cc 4
eloc 11
nc 6
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 maximum 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 or numeric
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
     * Checks whether $subArray is contained in $array
458
     *
459
     * @param array $array
460
     * @param array $subArray
461
     * @param bool  $strictComparison
462
     *
463
     * @return bool
464
     * @throws InvalidArgumentException
465
     */
466
    public static function isSubArray(array $array, array $subArray, $strictComparison = true)
467
    {
468
        if (!is_bool($strictComparison)) {
469
            throw new InvalidArgumentException('Strict comparison parameter must be a boolean');
470
        }
471
472
        $return = true;
473
        foreach ($subArray as $key => $value) {
474
            if (isset($array[$key]) || array_key_exists($key, $array)) {
475
                $check = $strictComparison ? $array[$key] !== $subArray[$key] : $array[$key] != $subArray[$key];
476
                if ($check) {
477
                    $return = false;
478
                    break;
479
                }
480
            } else {
481
                $return = false;
482
                break;
483
            }
484
        }
485
486
        return $return;
487
    }
488
489
    /**
490
     * Selects random sub array
491
     *
492
     * @param array $array
493
     * @param int $numberOfRequiredElements
494
     *
495
     * @return array
496
     * @throws InvalidArgumentException
497
     */
498
    public static function selectRandomArrayElements(array $array, $numberOfRequiredElements)
499
    {
500
        // validation, must be positive int or 0
501
        if (!self::isLogicallyCastableToInt($numberOfRequiredElements)) {
502
            throw new InvalidArgumentException('Number of requested elements parameter must be a positive integer');
503
        }
504
        if ($numberOfRequiredElements <= 0) {
505
            throw new InvalidArgumentException('Number of requested elements parameter must be a positive integer');
506
        }
507
508
        $selected = $array;
509
        if (count($array) > $numberOfRequiredElements) {
510
            // select required number of random keys
511
            $selectedKeys = array_rand($array, $numberOfRequiredElements);
512
            $selectedKeys = $numberOfRequiredElements == 1 ? [$selectedKeys] : $selectedKeys;
513
            // select only array members with selected random keys
514
            $selected = array_intersect_key($array, array_fill_keys($selectedKeys, null));
515
        }
516
517
        return $selected;
518
    }
519
520
    /**
521
     * @param array $matrix
522
     * @param mixed $column
523
     * @param mixed $value
524
     * @param bool  $insertIfMissing
525
     * @param bool  $overwrite
526
     *
527
     * @return array
528
     */
529
    public static function setColumn(array $matrix, $column, $value, $insertIfMissing = true, $overwrite = true)
530
    {
531
        // validate input
532
        if (self::countMinDepth($matrix) < 2) {
533
            throw new UnexpectedValueException('Can not set columns on one dimensional array');
534
        }
535
        if (!is_scalar($column)) {
536
            throw new InvalidArgumentException('Invalid column');
537
        }
538
        if (!is_bool($insertIfMissing)) {
539
            throw new InvalidArgumentException('Insert if missing indicator must be a boolean');
540
        }
541
        if (!is_bool($overwrite)) {
542
            throw new InvalidArgumentException('Overwrite indicator must be a boolean');
543
        }
544
545
        foreach ($matrix as $key => $row) {
546
            if (isset($row[$column]) || array_key_exists($column, $row)) {
547
                $matrix[$key][$column] = ($overwrite ? $value : $matrix[$key][$column]);
548
            } elseif ($insertIfMissing) {
549
                $matrix[$key][$column] = $value;
550
            }
551
        }
552
553
        return $matrix;
554
    }
555
556
    /**
557
     * Check whether casting a variable to int would conway useful information
558
     *
559
     * @param mixed $input
560
     *
561
     * @return bool
562
     */
563
    private static function isLogicallyCastableToInt($input)
564
    {
565
        return !is_bool($input) && filter_var($input, FILTER_VALIDATE_INT) !== false;
566
    }
567
}