Completed
Push — master ( 4ce22d...b7b9fb )
by Jean
02:22
created

Arrays   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 333
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 114
dl 0
loc 333
rs 7.44
c 0
b 0
f 0
wmc 52

8 Methods

Rating   Name   Duplication   Size   Complexity  
A isAssociative() 0 8 1
C merge() 0 65 17
A unique() 0 28 6
A mergePreservingDistincts() 0 28 4
B mergeRecursiveCustom() 0 52 9
A keepUniqueColumnValues() 0 15 5
A cleanMergeBuckets() 0 9 3
B weightedMean() 0 30 7

How to fix   Complexity   

Complex Class

Complex classes like Arrays often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Arrays, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace JClaveau\Arrays;
3
4
/**
5
 *
6
 */
7
class Arrays
8
{
9
    /**
10
     * Taken from Kohana's Arr class.
11
     *
12
     * Tests if an array is associative or not.
13
     *
14
     *     // Returns TRUE
15
     *     Arr::isAssoc(array('username' => 'john.doe'));
16
     *
17
     *     // Returns FALSE
18
     *     Arr::isAssoc('foo', 'bar');
19
     *
20
     * @param   array   $array  array to check
21
     * @return  boolean
22
     */
23
    public static function isAssociative(array $array)
24
    {
25
        // Keys of the array
26
        $keys = array_keys($array);
27
28
        // If the array keys of the keys match the keys, then the array must
29
        // not be associative (e.g. the keys array looked like {0:0, 1:1...}).
30
        return array_keys($keys) !== $keys;
31
    }
32
33
    /**
34
     * Taken from Kohana's Arr class.
35
     *
36
     * Recursively merge two or more arrays. Values in an associative array
37
     * overwrite previous values with the same key. Values in an indexed array
38
     * are appended, but only when they do not already exist in the result.
39
     *
40
     * Note that this does not work the same as [array_merge_recursive](http://php.net/array_merge_recursive)!
41
     *
42
     *     $john = array('name' => 'john', 'children' => array('fred', 'paul', 'sally', 'jane'));
43
     *     $mary = array('name' => 'mary', 'children' => array('jane'));
44
     *
45
     *     // John and Mary are married, merge them together
46
     *     $john = Arr::merge($john, $mary);
47
     *
48
     *     // The output of $john will now be:
49
     *     array('name' => 'mary', 'children' => array('fred', 'paul', 'sally', 'jane'))
50
     *
51
     * @param   array  $array1      initial array
52
     * @param   array  $array2,...  array to merge
53
     * @return  array
54
     */
55
    public static function merge($array1, $array2)
56
    {
57
        if (self::isAssociative($array2))
58
        {
59
            foreach ($array2 as $key => $value)
60
            {
61
                if (is_array($value)
62
                    AND isset($array1[$key])
63
                    AND is_array($array1[$key])
64
                )
65
                {
66
                    $array1[$key] = self::merge($array1[$key], $value);
67
                }
68
                else
69
                {
70
                    $array1[$key] = $value;
71
                }
72
            }
73
        }
74
        else
75
        {
76
            foreach ($array2 as $value)
77
            {
78
                if ( ! in_array($value, $array1, TRUE))
79
                {
80
                    $array1[] = $value;
81
                }
82
            }
83
        }
84
85
        if (func_num_args() > 2)
86
        {
87
            foreach (array_slice(func_get_args(), 2) as $array2)
0 ignored issues
show
introduced by
$array2 is overwriting one of the parameters of this function.
Loading history...
88
            {
89
                if (self::isAssociative($array2))
90
                {
91
                    foreach ($array2 as $key => $value)
92
                    {
93
                        if (is_array($value)
94
                            AND isset($array1[$key])
95
                            AND is_array($array1[$key])
96
                        )
97
                        {
98
                            $array1[$key] = self::merge($array1[$key], $value);
99
                        }
100
                        else
101
                        {
102
                            $array1[$key] = $value;
103
                        }
104
                    }
105
                }
106
                else
107
                {
108
                    foreach ($array2 as $value)
109
                    {
110
                        if ( ! in_array($value, $array1, TRUE))
111
                        {
112
                            $array1[] = $value;
113
                        }
114
                    }
115
                }
116
            }
117
        }
118
119
        return $array1;
120
    }
121
122
    /**
123
     * Equivalent of array_merge_recursive with more options.
124
     *
125
     * @param array         $existing_row
126
     * @param array         $conflict_row
127
     * @param callable|null $merge_resolver
128
     * @param int           $max_depth
129
     *
130
     * + If exist only in conflict row => add
131
     * + If same continue
132
     * + If different merge as array
133
     */
134
    public static function mergeRecursiveCustom(
135
        array $existing_row,
136
        array $conflict_row,
137
        callable $merge_resolver=null,
138
        $max_depth=null
139
    ){
140
        foreach ($conflict_row as $column => $conflict_value) {
141
142
            // not existing in first array
143
            if (!isset($existing_row[$column])) {
144
                $existing_row[$column] = $conflict_value;
145
                continue;
146
            }
147
148
            $existing_value = $existing_row[$column];
149
150
            // two arrays so we recurse
151
            if (is_array($existing_value) && is_array($conflict_value)) {
152
153
                if ($max_depth === null || $max_depth > 0) {
154
                    $existing_row[$column] = static::mergeRecursiveCustom(
155
                        $existing_value,
156
                        $conflict_value,
157
                        $merge_resolver,
158
                        $max_depth - 1
159
                    );
160
                    continue;
161
                }
162
            }
163
164
            if ($merge_resolver) {
165
                $existing_row[$column] = call_user_func_array(
166
                    $merge_resolver,
167
                    [
168
                        $existing_value,
169
                        $conflict_value,
170
                        $column,
171
                    ]
172
                );
173
            }
174
            else {
175
                // same resolution as array_merge_recursive
176
                if (!is_array($existing_value)) {
177
                    $existing_row[$column] = [$existing_value];
178
                }
179
180
                // We store the new value with their previous ones
181
                $existing_row[$column][] = $conflict_value;
182
            }
183
        }
184
185
        return $existing_row;
186
    }
187
188
    /**
189
     * Merges two rows
190
     *
191
     * @param  array $existing_row
192
     * @param  array $conflict_row
193
     *
194
     * @return array
195
     */
196
    public static function mergePreservingDistincts(
197
        array $existing_row,
198
        array $conflict_row
199
    ){
200
        $merge = static::mergeRecursiveCustom(
201
            $existing_row,
202
            $conflict_row,
203
            function ($existing_value, $conflict_value, $column) {
204
205
                if ( ! $existing_value instanceof MergeBucket) {
206
                    $existing_value = MergeBucket::from()->push($existing_value);
207
                }
208
209
                // We store the new value with their previous ones
210
                if ( ! $conflict_value instanceof MergeBucket) {
211
                    $conflict_value = MergeBucket::from()->push($conflict_value);
212
                }
213
214
                foreach ($conflict_value->toArray() as $conflict_key => $conflict_entry) {
215
                    $existing_value->push($conflict_entry);
216
                }
217
218
                return $existing_value;
219
            },
220
            0
221
        );
222
223
        return $merge;
224
    }
225
226
    /**
227
     *
228
     */
229
    public static function cleanMergeBuckets($merged_row)
230
    {
231
        foreach ($merged_row as $entry => $values) {
232
            if ($values instanceof MergeBucket) {
233
                $merged_row[ $entry ] = $values->toArray();
234
            }
235
        }
236
237
        return $merged_row;
238
    }
239
240
    /**
241
     * This is the cleaning part of self::mergePreservingDistincts()
242
     *
243
     * @see mergePreservingDistincts()
244
     */
245
    public static function keepUniqueColumnValues(array $row, array $excluded_columns=[])
246
    {
247
        foreach ($row as $column => &$values) {
248
            if (!is_array($values))
249
                continue;
250
251
            if (in_array($column, $excluded_columns))
252
                continue;
253
254
            $values = array_unique($values);
255
            if (count($values) == 1)
256
                $values = $values[0];
257
        }
258
259
        return $row;
260
    }
261
262
    /**
263
     * Replacement of array_unique, keeping the first key.
264
     *
265
     * @param  array|\Traversable $array
266
     * @return array|\Traversable With unique values
267
     *
268
     * @todo   Options to keep another key than the first one?
269
     */
270
    public static function unique($array)
271
    {
272
        if (! is_array($array) && ! $array instanceof \Traversable) {
0 ignored issues
show
introduced by
$array is always a sub-type of Traversable.
Loading history...
273
            throw new \InvalidArgumentException(
274
                "\$array must be an array or a \Traversable instead of: \n"
275
                .var_export($array, true)
276
            );
277
        }
278
279
        $ids = [];
280
        foreach ($array as $key => $value) {
281
            if (is_scalar($value)) {
282
                $id = $value;
283
            }
284
            else {
285
                $id = serialize($value);
286
            }
287
288
            if (isset($ids[ $id ])) {
289
                unset($array[ $key ]);
290
                $ids[ $id ][] = $key;
291
                continue;
292
            }
293
294
            $ids[ $id ] = [$key];
295
        }
296
297
        return $array;
298
    }
299
300
    /**
301
     * This method returns a classical mathemartic weighted mean.
302
     *
303
     * @todo It would ideally handled by a bridge with this fantastic math
304
     * lib https://github.com/markrogoyski/math-php/ but we need the support
305
     * of PHP 7 first.
306
     *
307
     * @see https://en.wikipedia.org/wiki/Weighted_arithmetic_mean
308
     * @see https://github.com/markrogoyski/math-php/
309
     */
310
    public static function weightedMean($values, $weights)
311
    {
312
        if ( ! is_array($values))
313
            $values = [$values];
314
315
        if ( ! is_array($weights))
316
            $weights = [$weights];
317
318
        if (count($values) != count($weights)) {
319
            throw new \InvalidArgumentException(
320
                "Different number of "
321
                ." values and weights for weight mean calculation: \n"
322
                .var_export($values,  true)."\n\n"
323
                .var_export($weights, true)
324
            );
325
        }
326
327
        if (!$values)
328
            return null;
329
330
        $weights_sum  = array_sum($weights);
331
        if (!$weights_sum)
332
            return 0;
333
334
        $weighted_sum = 0;
335
        foreach ($values as $i => $value) {
336
            $weighted_sum += $value * $weights[$i];
337
        }
338
339
        return $weighted_sum / $weights_sum;
340
    }
341
342
    /**/
343
}
344