Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.10% covered (warning)
87.10%
81 / 93
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
tao_helpers_Array
87.10% covered (warning)
87.10%
81 / 93
62.50% covered (warning)
62.50%
5 / 8
51.75
0.00% covered (danger)
0.00%
0 / 1
 sortByField
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 array_unique
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 isAssoc
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 containsOnlyValue
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
7
 arraysContainOnlyValue
96.15% covered (success)
96.15%
25 / 26
0.00% covered (danger)
0.00%
0 / 1
10
 minArrayCountValues
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
8
 countConsistentColumns
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
9
 isValidMatrix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2008-2010 (original work) Deutsche Institut für Internationale Pädagogische Forschung
19 *                         (under the project TAO-TRANSFER);
20 *               2009-2012 (update and modification) Public Research Centre Henri Tudor
21 *                         (under the project TAO-SUSTAIN & TAO-DEV);
22 *               2013 (update and modification) Open Assessment Technologies SA (under the project TAO-PRODUCT);
23 *
24 */
25
26/**
27 * Utility class on Arrays.
28 *
29 * @author Bertrand Chevrier, <bertrand.chevrier@tudor.lu>
30 * @package tao
31 */
32class tao_helpers_Array
33{
34    /**
35     * Sort an associative array on a key criterion. Uses sort or asort PHP
36     * functions to implement the sort depending on the value of the descending
37     * parameter.
38     *
39     * @author Bertrand Chevrier, <bertrand.chevrier@tudor.lu>
40     * @param  array input The associative array to sort.
41     * @param  string field The key criterion.
42     * @param  boolean (optional, default = Ascending sort) descending Descending or Ascending order.
43     * @return array An associative array.
44     */
45    public static function sortByField($input, $field, $descending = false)
46    {
47        $returnValue = [];
48
49        $sorted = [];
50        foreach ($input as $key => $value) {
51            $sorted[$key] = $value[$field];
52        }
53
54        if ($descending) {
55            arsort($sorted);
56        } else {
57            asort($sorted);
58        }
59
60        foreach ($sorted as $key => $value) {
61            $returnValue[$key] = $input[$key];
62        }
63
64        return (array) $returnValue;
65    }
66
67    /**
68     * remove duplicate from array of objects implementing the __equal() function
69     *
70     * @param array $array
71     * @return array $array
72     *
73     * phpcs:disable PSR1.Methods.CamelCapsMethodName
74     */
75    public static function array_unique($array)
76    {
77        $keys = array_keys($array);
78        $toDrop = [];
79        for ($i = count($keys) - 1; $i >= 0; $i--) {
80            for ($j = $i - 1; $j >= 0; $j--) {
81                if ($array[$keys[$i]]->__equals($array[$keys[$j]])) {
82                    $toDrop[] = $keys[$i];
83                    break;
84                }
85            }
86        }
87        foreach ($toDrop as $key) {
88            unset($array[$key]);
89        }
90        return $array;
91    }
92    // phpcs:enable PSR1.Methods.CamelCapsMethodName,PEAR.Functions.ValidDefaultValue
93
94    /**
95     * Test if ann array is associative or not
96     *
97     * taken from http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
98     *
99     * @param array $arr
100     * @return boolean
101     */
102    public static function isAssoc($arr)
103    {
104        return array_keys($arr) !== range(0, count($arr) - 1);
105    }
106
107    /**
108     * Does an array contains only a given value.
109     *
110     * Whether or not a given array contains only a given value.
111     *
112     * <code>
113     * // Example 1
114     * $container = [1, 1, 1]; $value = 1; // true
115     * $container = [1, 1, 2]; $value = 1; // false
116     * </code>
117     *
118     * When the $strict parameter is false, values contained in the $container array
119     * will be compared with $value using the PHP == operator. Otherwise, the comparison
120     * will be proceed with the PHP === operator.
121     *
122     * Some particular indexes of $container can be ignored with the help of the $exceptAtIndex parameter.
123     *
124     * <code>
125     * // Example 2
126     * $container = [1, 1, 2]; $value = 1; $exceptAtIndex = [2]; // true
127     * $container = [1, 1, 2]; $value = 1; $exceptAtIndex = [1]; // false
128     * </code>
129     *
130     * * When $value is not a scalar value, the method returns false.
131     * * When $container is empty, the method returns false.
132     *
133     * @param mixed $value
134     * @param array $container
135     * @param boolean $strict
136     * @param array $exceptAtIndex
137     * @return boolean
138     */
139    public static function containsOnlyValue($value, array $container, $strict = false, $exceptAtIndex = [])
140    {
141        if (!is_scalar($value)) {
142            return false;
143        }
144
145        if (empty($container)) {
146            return false;
147        }
148
149        $matchCount = 0;
150
151        foreach ($container as $key => $val) {
152            if (in_array($key, $exceptAtIndex, true)) {
153                continue;
154            }
155
156            $match = ($strict === false) ? $value == $val : $value === $val;
157
158            if (!$match) {
159                return false;
160            } else {
161                $matchCount++;
162            }
163        }
164
165        return $matchCount !== 0;
166    }
167
168    /**
169     * Does a collection of arrays contain only a given value.
170     *
171     * Whether or not a given collection of arrays contain only a given $value.
172     *
173     * * You can specify that some indexes of contained arrays do not have to match $value by setting the $exceptAtIndex
174     *   array with the indexes to be ignored.
175     * * When $exceptNContainers > 0, it is allowed that $containers contains $exceptNContainers arrays not matching
176     *   $value.
177     * * The $invalidContainers parameter is an optional reference that will be filled with the index of arrays from
178     *   $containers that do not contain the $value value only.
179     * * The $validContainers parameter is an optional reference that will be filled with the index of array from
180     *   $containers that do contain the $value value only.
181     *
182     * @param array $containers
183     * @param mixed $value
184     * @param integer $exceptNContainers
185     * @param array $exceptAtIndex
186     * @param array $invalidContainers
187     * @param array $validContainers
188     * @return boolean
189     */
190    public static function arraysContainOnlyValue(
191        array $containers,
192        $value,
193        $exceptNContainers = 0,
194        array $exceptAtIndex = [],
195        array &$invalidContainers = [],
196        array &$validContainers = []
197    ) {
198        if ($exceptNContainers < 0) {
199            $exceptNContainers = 0;
200        } else {
201            $exceptNContainers = intval($exceptNContainers);
202        }
203
204        $validCount = 0;
205        $rowCount = 0;
206
207        $expectedValidCount = count($containers) - intval($exceptNContainers);
208
209        foreach ($containers as $row) {
210            if (!is_array($row)) {
211                return false;
212            }
213
214            $valid = true;
215            $exceptCount = 0;
216
217            for ($i = 0; $i < count($row); $i++) {
218                if (in_array($i, $exceptAtIndex, true)) {
219                    $exceptCount++;
220                    continue;
221                } elseif ($row[$i] !== $value) {
222                    $valid = false;
223                    break;
224                }
225            }
226
227            if ($exceptCount !== 0 && $exceptCount === count($row)) {
228                $valid = false;
229            }
230
231            if ($valid == true) {
232                $validContainers[] = $rowCount;
233                $validCount++;
234            } else {
235                $invalidContainers[] = $rowCount;
236            }
237
238            $rowCount++;
239        }
240
241        return $validCount === $expectedValidCount;
242    }
243
244    /**
245     * Detect Row with Minimum of Value(s)
246     *
247     * This method helps you to dectect which is the array contained in $arrays containing
248     * the less amount of specific value(s).
249     *
250     * Pleae note that the detection comparison is NOT strict (using the == PHP operator).
251     *
252     * @param mixed $values Can be either scalar or array.
253     * @param array $arrays An array of arrays.
254     * @param boolean $returnAll Wheter or not return an array of keys when some amounts of specific values are similar
255     *                           accross $arrays.
256     * @return mixed Key(s) of the array(s) containing the less amount of $values. or false if it not possible to
257     *               designate a row.
258     */
259    public static function minArrayCountValues($values, array $arrays, $returnAll = false)
260    {
261        if (!is_array($values)) {
262            $values = [$values];
263        }
264
265        $counts = [];
266
267        foreach ($arrays as $index => $arr) {
268            $counts[$index] = 0;
269
270            if (!is_array($arr)) {
271                return false;
272            }
273
274            $arrayCountValues = array_count_values($arr);
275
276            foreach ($values as $value) {
277                $keys = array_keys($arrayCountValues);
278                if (($search = array_search($value, $keys)) !== false) {
279                    $counts[$index] += $arrayCountValues[$keys[$search]];
280                }
281            }
282        }
283
284        if (count($counts) > 0) {
285            $mins = array_keys($counts, min($counts));
286
287            return ($returnAll) ? $mins : $mins[0];
288        } else {
289            return false;
290        }
291    }
292
293    /**
294     * Count the Amount of Consistent Columns in Matrix
295     *
296     * This method aims at counting the number of columns in a $matrix containing strictly similar values.
297     *
298     * @param array $matrix An array containing exclusively arrays.
299     * @param array $ignoreValues An array of values to be ignored while comparing values within columns.
300     * @param array $emptyIsConsistent (optional) Consider empty columns (after ignoring values) as consistent.
301     * @return mixed The amount of consistent columns in $matrix or false if $matrix is not a well formed matrix.
302     */
303    public static function countConsistentColumns(array $matrix, array $ignoreValues = [], $emptyIsConsistent = false)
304    {
305        $consistentCount = 0;
306
307        if (!self::isValidMatrix($matrix)) {
308            return $consistentCount;
309        }
310
311        for ($i = 0; $i < count($matrix[0]); $i++) {
312            $column = array_column($matrix, $i);
313            if (count($column) !== count($matrix)) {
314                // Malformed matrix.
315                return false;
316            }
317
318            $column = array_unique($column);
319
320            foreach ($ignoreValues as $ignoreVal) {
321                if (($search = array_search($ignoreVal, $column, true)) !== false) {
322                    unset($column[$search]);
323                }
324            }
325
326            if (count($column) === 1) {
327                $consistentCount++;
328            } elseif (count($column) === 0 && $emptyIsConsistent) {
329                $consistentCount++;
330            }
331        }
332
333        return $consistentCount;
334    }
335
336    /**
337     * Checks if provided matrix satisfies requirements.
338     *   - as of PHP 7.2 count() expect array or countable object and will emit an E_WARNING in other cases.
339     *
340     * @param array $matrix
341     * @return bool
342     */
343    private static function isValidMatrix(array $matrix)
344    {
345        return is_array($matrix) && count($matrix) > 0 && is_array($matrix[0]);
346    }
347}