Completed
Push — master ( c47d45...d68738 )
by Aleksandar
02:02
created

SimpleArrayLibrary::isAssociative()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
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 whether array has only provided keys as indexes
337
     *
338
     * @param array $haystack
339
     * @param array $needles
340
     *
341
     * @return bool
342
     */
343
    public static function hasOnlyKeys(array $haystack, array $needles)
344
    {
345
        return self::haveSameKeys($haystack, array_flip($needles));
346
    }
347
348
    /**
349
     * Checks if two arrays have all equal keys
350
     *
351
     * @deprecated
352
     *
353
     * @param array $array1
354
     * @param array $array2
355
     *
356
     * @return boolean
357
     */
358
    public static function haveEqualKeys(array $array1, array $array2)
359
    {
360
        return self::hasAllKeys($array1, array_keys($array2)) && self::hasAllKeys($array2, array_keys($array1)) ? true : false;
361
    }
362
363
    /**
364
     * Check if two arrays have all equal values
365
     *
366
     * @deprecated
367
     *
368
     * @param array $array1
369
     * @param array $array2
370
     *
371
     * @return bool
372
     */
373
    public static function haveEqualValues($array1, $array2)
374
    {
375
        return self::hasAllValues($array1, $array2) && self::hasAllValues($array2, $array1) ? true : false;
376
    }
377
378
    /**
379
     * Checks if two arrays have all equal keys
380
     *
381
     * @param array $array1
382
     * @param array $array2
383
     *
384
     * @return boolean
385
     */
386
    public static function haveSameKeys(array $array1, array $array2)
387
    {
388
        return self::hasAllKeys($array1, array_keys($array2)) && self::hasAllKeys($array2, array_keys($array1)) ? true : false;
389
    }
390
391
    /**
392
     * Check if two arrays have all equal values
393
     *
394
     * @param array $array1
395
     * @param array $array2
396
     *
397
     * @return bool
398
     */
399
    public static function haveSameValues($array1, $array2)
400
    {
401
        return self::hasAllValues($array1, $array2) && self::hasAllValues($array2, $array1) ? true : false;
402
    }
403
404
    /**
405
     * @param mixed $array
406
     * @param mixed $subArray
407
     * @param bool  $overwrite
408
     * @param bool  $ignoreIfExists
409
     *
410
     * @return array
411
     */
412
    public static function insertSubArray($array, $subArray, $overwrite = false, $ignoreIfExists = false)
413
    {
414
        // validate
415
        if (!is_bool($overwrite)) {
416
            throw new InvalidArgumentException('Overwrite indicator must be a boolean');
417
        }
418
        if (!is_bool($ignoreIfExists)) {
419
            throw new InvalidArgumentException('Ignore if exists indicator must be a boolean');
420
        }
421
422
        if (!is_array($subArray) || !is_array($array)) {
423
            // $subArray[$key] is leaf of the array
424
            if ($overwrite) {
425
                $array = $subArray;
426
            } elseif (!$ignoreIfExists) {
427
                throw new UnexpectedValueException('Sub-array already exists');
428
            }
429
        } else {
430
            $key = key($subArray);
431
            if (isset($array[$key]) || array_key_exists($key, $array)) {
432
                $array[$key] = self::insertSubArray($array[$key], $subArray[$key], $overwrite, $ignoreIfExists);
433
            } else {
434
                $array[$key] = $subArray[$key];
435
            }
436
        }
437
438
        return $array;
439
    }
440
441
    /**
442
     * Checks whether array is associative or numeric
443
     *
444
     * @param array $array
445
     *
446
     * @return bool
447
     */
448
    public static function isAssociative(array $array)
449
    {
450
        return (bool)count(array_filter(array_keys($array), 'is_string'));
451
    }
452
453
    /**
454
     * Checks whether $subArray is contained in $array
455
     *
456
     * @param array $array
457
     * @param array $subArray
458
     * @param bool  $strictComparison
459
     *
460
     * @return bool
461
     * @throws InvalidArgumentException
462
     */
463
    public static function isSubArray(array $array, array $subArray, $strictComparison = true)
464
    {
465
        if (!is_bool($strictComparison)) {
466
            throw new InvalidArgumentException('Strict comparison parameter must be a boolean');
467
        }
468
469
        $return = true;
470
        foreach ($subArray as $key => $value) {
471
            if (isset($array[$key]) || array_key_exists($key, $array)) {
472
                $check = $strictComparison ? $array[$key] !== $subArray[$key] : $array[$key] != $subArray[$key];
473
                if ($check) {
474
                    $return = false;
475
                    break;
476
                }
477
            } else {
478
                $return = false;
479
                break;
480
            }
481
        }
482
483
        return $return;
484
    }
485
486
    /**
487
     * Selects random sub array
488
     *
489
     * @param array $array
490
     * @param int $numberOfRequiredElements
491
     *
492
     * @return array
493
     * @throws InvalidArgumentException
494
     */
495
    public static function selectRandomArrayElements(array $array, $numberOfRequiredElements)
496
    {
497
        // validation, must be positive int or 0
498
        if (!self::isLogicallyCastableToInt($numberOfRequiredElements)) {
499
            throw new InvalidArgumentException('Number of requested elements parameter must be a positive integer');
500
        }
501
        if ($numberOfRequiredElements <= 0) {
502
            throw new InvalidArgumentException('Number of requested elements parameter must be a positive integer');
503
        }
504
505
        $selected = $array;
506
        if (count($array) > $numberOfRequiredElements) {
507
            // select required number of random keys
508
            $selectedKeys = array_rand($array, $numberOfRequiredElements);
509
            $selectedKeys = $numberOfRequiredElements == 1 ? [$selectedKeys] : $selectedKeys;
510
            // select only array members with selected random keys
511
            $selected = array_intersect_key($array, array_fill_keys($selectedKeys, null));
512
        }
513
514
        return $selected;
515
    }
516
517
    /**
518
     * @param array $matrix
519
     * @param mixed $column
520
     * @param mixed $value
521
     * @param bool  $insertIfMissing
522
     * @param bool  $overwrite
523
     *
524
     * @return array
525
     */
526
    public static function setColumn(array $matrix, $column, $value, $insertIfMissing = true, $overwrite = true)
527
    {
528
        // validate input
529
        if (self::countMinDepth($matrix) < 2) {
530
            throw new UnexpectedValueException('Can not set columns on one dimensional array');
531
        }
532
        if (!is_scalar($column)) {
533
            throw new InvalidArgumentException('Invalid column');
534
        }
535
        if (!is_bool($insertIfMissing)) {
536
            throw new InvalidArgumentException('Insert if missing indicator must be a boolean');
537
        }
538
        if (!is_bool($overwrite)) {
539
            throw new InvalidArgumentException('Overwrite indicator must be a boolean');
540
        }
541
542
        foreach ($matrix as $key => $row) {
543
            if (isset($row[$column]) || array_key_exists($column, $row)) {
544
                $matrix[$key][$column] = ($overwrite ? $value : $matrix[$key][$column]);
545
            } elseif ($insertIfMissing) {
546
                $matrix[$key][$column] = $value;
547
            }
548
        }
549
550
        return $matrix;
551
    }
552
553
    /**
554
     * Check whether casting a variable to int would conway useful information
555
     *
556
     * @param mixed $input
557
     *
558
     * @return bool
559
     */
560
    private static function isLogicallyCastableToInt($input)
561
    {
562
        return !is_bool($input) && filter_var($input, FILTER_VALIDATE_INT) !== false;
563
    }
564
}
565