Passed
Push — master ( 2baaa2...30c2bf )
by Nick
01:23
created

AssociativeArray::quickGroup()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 2
dl 0
loc 17
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Associative array class.
5
 *
6
 * @author  Nick Lai <[email protected]>
7
 * @license https://opensource.org/licenses/MIT MIT
8
 * @link    https://github.com/nick-lai/associative-array
9
 */
10
11
namespace NickLai;
12
13
use ArrayAccess;
14
use ArrayIterator;
15
use Countable;
16
use IteratorAggregate;
17
use Traversable;
18
19
class AssociativeArray implements ArrayAccess, Countable, IteratorAggregate
20
{
21
    /**
22
     * The rows contained in the associative array.
23
     *
24
     * @var array
25
     */
26
    protected $rows = [];
27
28
    /**
29
     * Create a new associative array.
30
     *
31
     * @param mixed $rows
32
     * @return void
33
     */
34
    public function __construct($rows = [])
35
    {
36
        $this->rows = $this->getAssociativeRows($rows);
37
    }
38
39
    /**
40
     * Create a new associative array instance.
41
     *
42
     * @param mixed $rows
43
     * @return static
44
     */
45
    public static function make($rows = [])
46
    {
47
        return new static($rows);
48
    }
49
50
    /**
51
     * Get rows of selected columns.
52
     *
53
     * @param string|array $keys
54
     * @return static
55
     */
56
    public function select($keys)
57
    {
58
        if (!is_array($keys)) {
59
            $keys = (array)$keys;
60
        }
61
62
        $keys = array_flip($keys);
63
64
        return new static(array_map(function($row) use ($keys) {
65
            return array_intersect_key($row, $keys);
66
        }, $this->rows));
67
    }
68
69
    /**
70
     * Filter the rows using the given callback.
71
     *
72
     * @param callable $callback
73
     * @return static
74
     */
75
    public function where(callable $callback)
76
    {
77
        return new static(array_filter($this->rows, $callback, ARRAY_FILTER_USE_BOTH));
78
    }
79
80
    /**
81
     * Inner join rows
82
     *
83
     * @param array $rows
84
     * @param callable $on
85
     * @return static
86
     */
87
    public function innerJoin($rows, callable $on)
88
    {
89
        $result = [];
90
91
        foreach ($this->rows as $leftRow) {
92
            foreach ($rows as $rightRow) {
93
                if ($on($leftRow, $rightRow)) {
94
                    $result[] = $leftRow + $rightRow;
95
                    break;
96
                }
97
            }
98
        }
99
100
        return new static($result);
101
    }
102
103
    /**
104
     * Left join rows
105
     *
106
     * @param array $rows
107
     * @param callable $on
108
     * @return static
109
     */
110
    public function leftJoin($rows, callable $on)
111
    {
112
        $nullRightRow = [];
113
114
        foreach ((new static($rows))->first() as $key => $value) {
115
            $nullRightRow[$key] = null;
116
        }
117
118
        $result = [];
119
120
        foreach ($this->rows as $leftRow) {
121
            $row = $leftRow + $nullRightRow;
122
            foreach ($rows as $rightRow) {
123
                if ($on($leftRow, $rightRow)) {
124
                    $row = $leftRow + $rightRow;
125
                    break;
126
                }
127
            }
128
            $result[] = $row;
129
        }
130
131
        return new static($result);
132
    }
133
134
    /**
135
     * Right join rows
136
     *
137
     * @param array $rows
138
     * @param callable $on
139
     * @return static
140
     */
141
    public function rightJoin($rows, callable $on)
142
    {
143
        return (new static($rows))->leftJoin($this->rows, $on);
144
    }
145
146
    /**
147
     * Order by keys
148
     *
149
     * @param string|array $keys
150
     * @param string|array $directions
151
     * @return static
152
     */
153
    public function orderBy($keys, $directions = 'asc')
154
    {
155
        if (!is_array($keys)) {
156
            $keys = (array)$keys;
157
        }
158
159
        $key2IsDesc = [];
160
161
        if (is_string($directions)) {
162
            $isDesc = $directions === 'desc';
163
            foreach ($keys as $key) {
164
                $key2IsDesc[$key] = $isDesc;
165
            }
166
        } else {
167
            $i = 0;
168
            foreach ($keys as $key) {
169
                $key2IsDesc[$key] = (($directions[$i++] ?? 'asc') === 'desc');
170
            }
171
        }
172
173
        $result = $this->rows;
174
175
        usort($result, function($a, $b) use ($keys, $key2IsDesc) {
176
            foreach ($keys as $key) {
177
                if ($comparedResult = $key2IsDesc[$key]
178
                        ? $b[$key] <=> $a[$key]
179
                        : $a[$key] <=> $b[$key]) {
180
                    return $comparedResult;
181
                }
182
            }
183
            return 0;
184
        });
185
186
        return new static($result);
187
    }
188
189
    /**
190
     * Groups an associative array by keys.
191
     *
192
     * @param array|string $keys
193
     * @return array
194
     */
195
    public function groupBy($keys)
196
    {
197
        if (!is_array($keys)) {
198
            $keys = (array)$keys;
199
        }
200
201
        return self::quickGroup($this->rows, array_reverse($keys));
202
    }
203
204
    /**
205
     * Return the first row
206
     *
207
     * @param mixed $default
208
     * @return mixed
209
     */
210
    public function first($default = null)
211
    {
212
        foreach ($this->rows as $row) {
213
            return $row;
214
        }
215
216
        return $default;
217
    }
218
219
    /**
220
     * Return the last row
221
     *
222
     * @param mixed $default
223
     * @return mixed
224
     */
225
    public function last($default = null)
226
    {
227
        foreach (array_reverse($this->rows) as $row) {
228
            return $row;
229
        }
230
231
        return $default;
232
    }
233
234
    /**
235
     * Count the number of rows in the associative array.
236
     *
237
     * @return int
238
     */
239
    public function count()
240
    {
241
        return count($this->rows);
242
    }
243
244
    /**
245
     * Get the sum of a given key.
246
     *
247
     * @param string $key
248
     * @return mixed
249
     */
250
    public function sum($key)
251
    {
252
        return array_sum(array_column($this->rows, $key));
253
    }
254
255
    /**
256
     * Get the average value of a given key.
257
     *
258
     * @param string $key
259
     * @return mixed
260
     */
261
    public function avg($key)
262
    {
263
        $sum = $this->sum($key);
264
        return $sum ? ($sum / $this->count()) : $sum;
265
    }
266
267
    /**
268
     * Get the instance as an array.
269
     *
270
     * @return array
271
     */
272
    public function toArray()
273
    {
274
        return array_map(function($row) {
275
            return $row instanceof self ? $row->toArray() : $row;
276
        }, $this->rows);
277
    }
278
279
    /**
280
     * Get an iterator for the rows.
281
     *
282
     * @return \ArrayIterator
283
     */
284
    public function getIterator()
285
    {
286
        return new ArrayIterator($this->rows);
287
    }
288
289
    /**
290
     * Determine if a row exists at an offset.
291
     *
292
     * @param mixed $offset
293
     * @return bool
294
     */
295
    public function offsetExists($offset)
296
    {
297
        return array_key_exists($offset, $this->rows);
298
    }
299
300
    /**
301
     * Get a row at a given offset.
302
     *
303
     * @param mixed $offset
304
     * @return mixed
305
     */
306
    public function offsetGet($offset)
307
    {
308
        return $this->rows[$offset];
309
    }
310
311
    /**
312
     * Set the row at a given offset.
313
     *
314
     * @param mixed $offset
315
     * @param mixed $row
316
     * @return void
317
     */
318
    public function offsetSet($offset, $row)
319
    {
320
        if (is_null($offset)) {
321
            $this->rows[] = $row;
322
        } else {
323
            $this->rows[$offset] = $row;
324
        }
325
    }
326
327
    /**
328
     * Unset the row at a given offset.
329
     *
330
     * @param string $offset
331
     * @return void
332
     */
333
    public function offsetUnset($offset)
334
    {
335
        unset($this->rows[$offset]);
336
    }
337
338
    /**
339
     * Quick grouping.
340
     *
341
     * @param array $rows
342
     * @param array $keys
343
     *
344
     * @return array
345
     */
346
    protected static function quickGroup(array $rows, array $keys)
347
    {
348
        $result = [];
349
350
        $groupKey = array_pop($keys);
351
352
        foreach ($rows as $row) {
353
            $result[$row[$groupKey]][] = $row;
354
        }
355
356
        if (count($keys)) {
357
            foreach ($result as $groupBy => $groupedRows) {
358
                $result[$groupBy] = self::quickGroup($groupedRows, $keys);
359
            }
360
        }
361
362
        return $result;
363
    }
364
365
    /**
366
     * Results array of rows from associative array or traversable.
367
     *
368
     * @param mixed $rows
369
     * @return array
370
     */
371
    protected function getAssociativeRows($rows)
372
    {
373
        if (is_array($rows)) {
374
            return $rows;
375
        } elseif ($rows instanceof self) {
376
            return $rows->toArray();
377
        } elseif ($rows instanceof Traversable) {
378
            return iterator_to_array($rows);
379
        }
380
381
        return (array)$rows;
382
    }
383
}
384