|
1
|
|
|
<?php |
|
2
|
|
|
/* |
|
3
|
|
|
* Nozavroni/Collections |
|
4
|
|
|
* Just another collections library for PHP5.6+. |
|
5
|
|
|
* |
|
6
|
|
|
* @copyright Copyright (c) 2016 Luke Visinoni <[email protected]> |
|
7
|
|
|
* @author Luke Visinoni <[email protected]> |
|
8
|
|
|
* @license https://github.com/nozavroni/collections/blob/master/LICENSE The MIT License (MIT) |
|
9
|
|
|
*/ |
|
10
|
|
|
namespace Noz; |
|
11
|
|
|
|
|
12
|
|
|
use InvalidArgumentException; |
|
13
|
|
|
use Noz\Immutable\Sequence; |
|
14
|
|
|
use OutOfBoundsException; |
|
15
|
|
|
|
|
16
|
|
|
use ArrayIterator; |
|
17
|
|
|
use Countable; |
|
18
|
|
|
use Iterator; |
|
19
|
|
|
use ReflectionFunction; |
|
20
|
|
|
use Traversable; |
|
21
|
|
|
|
|
22
|
|
|
use Noz\Contracts\Arrayable; |
|
23
|
|
|
use Noz\Contracts\Invokable; |
|
24
|
|
|
use Noz\Contracts\CollectionInterface; |
|
25
|
|
|
|
|
26
|
|
|
use Noz\Traits\IsArrayable; |
|
27
|
|
|
use Noz\Traits\IsContainer; |
|
28
|
|
|
use Noz\Traits\IsSerializable; |
|
29
|
|
|
|
|
30
|
|
|
use function |
|
31
|
|
|
Noz\is_traversable, |
|
32
|
|
|
Noz\typeof, |
|
33
|
|
|
Noz\collect, |
|
34
|
|
|
Noz\is_arrayable, |
|
35
|
|
|
Noz\to_array, |
|
36
|
|
|
Noz\traversable_to_array, |
|
37
|
|
|
Noz\normalize_offset; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* Class Collection. |
|
41
|
|
|
* |
|
42
|
|
|
* This is the abstract class that all other collection classes are based on. |
|
43
|
|
|
* Although it's possible to use a completely custom Collection class by simply |
|
44
|
|
|
* implementing the "Collectable" interface, extending this class gives you a |
|
45
|
|
|
* whole slew of convenient methods for free. |
|
46
|
|
|
* |
|
47
|
|
|
* @package Noz\Immutable |
|
48
|
|
|
* |
|
49
|
|
|
* @author Luke Visinoni <[email protected]> |
|
50
|
|
|
* @copyright Copyright (c) 2016 Luke Visinoni <[email protected]> |
|
51
|
|
|
* |
|
52
|
|
|
* @todo Implement Serializable, other Interfaces |
|
53
|
|
|
*/ |
|
54
|
|
|
class Collection implements |
|
55
|
|
|
CollectionInterface, |
|
56
|
|
|
Arrayable, |
|
57
|
|
|
Invokable, |
|
58
|
|
|
Countable, |
|
59
|
|
|
Iterator |
|
60
|
|
|
{ |
|
61
|
|
|
use IsArrayable, |
|
62
|
|
|
IsContainer, |
|
63
|
|
|
IsSerializable; |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* @var array The collection of data this object represents |
|
67
|
|
|
*/ |
|
68
|
|
|
private $data = []; |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* @var bool True unless we have advanced past the end of the data array |
|
72
|
|
|
*/ |
|
73
|
|
|
protected $isValid = true; |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* AbstractCollection constructor. |
|
77
|
|
|
* |
|
78
|
|
|
* @param mixed $data The data to wrap |
|
79
|
|
|
*/ |
|
80
|
|
|
public function __construct($data = null) |
|
81
|
|
|
{ |
|
82
|
|
|
if (is_null($data)) { |
|
83
|
|
|
$data = []; |
|
84
|
|
|
} |
|
85
|
|
|
if (!is_traversable($data)) { |
|
86
|
|
|
throw new InvalidArgumentException(sprintf( |
|
87
|
|
|
'Invalid input for %s. Expecting traversable data, got "%s".', |
|
88
|
|
|
__METHOD__, |
|
89
|
|
|
typeof($data) |
|
90
|
|
|
)); |
|
91
|
|
|
} |
|
92
|
|
|
$this->setData($data); |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
public function __invoke() |
|
96
|
|
|
{ |
|
97
|
|
|
// $args = collect(func_get_args()); |
|
|
|
|
|
|
98
|
|
|
// if ($args->hasOffset(0)) { |
|
99
|
|
|
// if ($args->hasOffset(1)) { |
|
100
|
|
|
// // two args only... |
|
101
|
|
|
// return $this->set($args->getOffset(0), $args->getOffset(1)); |
|
102
|
|
|
// } |
|
103
|
|
|
// // one arg only... |
|
104
|
|
|
// $arg1 = $args->getOffset(0); |
|
105
|
|
|
// if (is_scalar($arg1)) { |
|
106
|
|
|
// return $this->get($arg1); |
|
107
|
|
|
// } |
|
108
|
|
|
// if (is_traversable($arg1)) { |
|
109
|
|
|
// return $this->union($arg1); |
|
110
|
|
|
// } |
|
111
|
|
|
// // @todo Should probably throw ane invalid arg exception here... |
|
112
|
|
|
// } |
|
113
|
|
|
return $this->toArray(); |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
/** |
|
117
|
|
|
* Set underlying data array. |
|
118
|
|
|
* |
|
119
|
|
|
* Sets the collection data. This method should NEVER be called anywhere other than in __construct(). |
|
120
|
|
|
* |
|
121
|
|
|
* @param array|Traversable $data The data to wrap |
|
122
|
|
|
*/ |
|
123
|
|
|
private function setData($data) |
|
124
|
|
|
{ |
|
125
|
|
|
$this->data = traversable_to_array($data); |
|
126
|
|
|
$this->rewind(); |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
/** |
|
130
|
|
|
* Get copy of underlying data array. |
|
131
|
|
|
* |
|
132
|
|
|
* Returns a copy of this collection's underlying data array. It returns a copy because collections are supposed to |
|
133
|
|
|
* be immutable. Nothing outside of the constructor should ever have direct access to the actual underlying array. |
|
134
|
|
|
* |
|
135
|
|
|
* @return array |
|
136
|
|
|
*/ |
|
137
|
|
|
protected function getData() |
|
138
|
|
|
{ |
|
139
|
|
|
return $this->data; |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
/** |
|
143
|
|
|
* @inheritDoc |
|
144
|
|
|
*/ |
|
145
|
|
|
public function count() |
|
146
|
|
|
{ |
|
147
|
|
|
return count($this->getData()); |
|
148
|
|
|
} |
|
149
|
|
|
|
|
150
|
|
|
/** |
|
151
|
|
|
* Return the current element. |
|
152
|
|
|
* |
|
153
|
|
|
* Returns the current element in the collection. The internal array pointer |
|
154
|
|
|
* of the data array wrapped by the collection should not be advanced by this |
|
155
|
|
|
* method. No side effects. Return current element only. |
|
156
|
|
|
* |
|
157
|
|
|
* @return mixed |
|
158
|
|
|
*/ |
|
159
|
|
|
public function current() |
|
160
|
|
|
{ |
|
161
|
|
|
return current($this->data); |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
/** |
|
165
|
|
|
* Return the current key. |
|
166
|
|
|
* |
|
167
|
|
|
* Returns the current key in the collection. No side effects. |
|
168
|
|
|
* |
|
169
|
|
|
* @return mixed |
|
170
|
|
|
*/ |
|
171
|
|
|
public function key() |
|
172
|
|
|
{ |
|
173
|
|
|
return key($this->data); |
|
174
|
|
|
} |
|
175
|
|
|
|
|
176
|
|
|
/** |
|
177
|
|
|
* Advance the internal pointer forward. |
|
178
|
|
|
* |
|
179
|
|
|
* Although this method will return the current value after advancing the |
|
180
|
|
|
* pointer, you should not expect it to. The interface does not require it |
|
181
|
|
|
* to return any value at all. |
|
182
|
|
|
* |
|
183
|
|
|
* @return mixed |
|
184
|
|
|
*/ |
|
185
|
|
|
public function next() |
|
186
|
|
|
{ |
|
187
|
|
|
$next = next($this->data); |
|
188
|
|
|
$key = key($this->data); |
|
189
|
|
|
if (isset($key)) { |
|
190
|
|
|
return $next; |
|
191
|
|
|
} |
|
192
|
|
|
$this->isValid = false; |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
/** |
|
196
|
|
|
* Rewind the internal pointer. |
|
197
|
|
|
* |
|
198
|
|
|
* Return the internal pointer to the first element in the collection. Again, |
|
199
|
|
|
* this method is not required to return anything by its interface, so you |
|
200
|
|
|
* should not count on a return value. |
|
201
|
|
|
* |
|
202
|
|
|
* @return mixed |
|
203
|
|
|
*/ |
|
204
|
|
|
public function rewind() |
|
205
|
|
|
{ |
|
206
|
|
|
$this->isValid = !empty($this->data); |
|
207
|
|
|
|
|
208
|
|
|
return reset($this->data); |
|
209
|
|
|
} |
|
210
|
|
|
|
|
211
|
|
|
/** |
|
212
|
|
|
* Is internal pointer in a valid position? |
|
213
|
|
|
* |
|
214
|
|
|
* If the internal pointer is advanced beyond the end of the collection, this method will return false. |
|
215
|
|
|
* |
|
216
|
|
|
* @return bool True if internal pointer isn't past the end |
|
217
|
|
|
*/ |
|
218
|
|
|
public function valid() |
|
219
|
|
|
{ |
|
220
|
|
|
return $this->isValid; |
|
221
|
|
|
} |
|
222
|
|
|
|
|
223
|
|
|
/** |
|
224
|
|
|
* @inheritDoc |
|
225
|
|
|
*/ |
|
226
|
|
|
public function sort($alg = null) |
|
227
|
|
|
{ |
|
228
|
|
|
if (is_null($alg)) { |
|
229
|
|
|
$alg = 'strnatcasecmp'; |
|
230
|
|
|
} |
|
231
|
|
|
uasort($this->data, $alg); |
|
232
|
|
|
|
|
233
|
|
|
return $this; |
|
234
|
|
|
} |
|
235
|
|
|
|
|
236
|
|
|
/** |
|
237
|
|
|
* @inheritDoc |
|
238
|
|
|
*/ |
|
239
|
|
|
public function sortkeys($alg = null) |
|
240
|
|
|
{ |
|
241
|
|
|
if (is_null($alg)) { |
|
242
|
|
|
$alg = 'strnatcasecmp'; |
|
243
|
|
|
} |
|
244
|
|
|
uksort($this->data, $alg); |
|
245
|
|
|
|
|
246
|
|
|
return $this; |
|
247
|
|
|
} |
|
248
|
|
|
|
|
249
|
|
|
/** |
|
250
|
|
|
* Does this collection have a value at given index? |
|
251
|
|
|
* |
|
252
|
|
|
* @param mixed $index The index to check |
|
253
|
|
|
* |
|
254
|
|
|
* @return bool |
|
255
|
|
|
*/ |
|
256
|
|
|
public function has($index) |
|
257
|
|
|
{ |
|
258
|
|
|
return array_key_exists($index, $this->getData()); |
|
259
|
|
|
} |
|
260
|
|
|
|
|
261
|
|
|
/** |
|
262
|
|
|
* Set value at given index. |
|
263
|
|
|
* |
|
264
|
|
|
* This method simulates setting a value in this collection, but because collections are immutable, it actually |
|
265
|
|
|
* returns a copy of this collection with the value in the new collection set to specified value. |
|
266
|
|
|
* |
|
267
|
|
|
* @param mixed $index The index to set a value at |
|
268
|
|
|
* @param mixed $val The value to set $index to |
|
269
|
|
|
* |
|
270
|
|
|
* @return $this |
|
271
|
|
|
*/ |
|
272
|
|
|
public function set($index, $val) |
|
273
|
|
|
{ |
|
274
|
|
|
$this->data[$index] = $val; |
|
275
|
|
|
|
|
276
|
|
|
return $this; |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
/** |
|
280
|
|
|
* Unset (delete) value at the given index. |
|
281
|
|
|
* |
|
282
|
|
|
* Get copy of collection with given index removed. |
|
283
|
|
|
* |
|
284
|
|
|
* @param mixed $index The index to unset |
|
285
|
|
|
* |
|
286
|
|
|
* @return Collection |
|
287
|
|
|
*/ |
|
288
|
|
|
public function delete($index) |
|
289
|
|
|
{ |
|
290
|
|
|
unset($this->data[$index]); |
|
291
|
|
|
|
|
292
|
|
|
return $this; |
|
293
|
|
|
} |
|
294
|
|
|
|
|
295
|
|
|
/** |
|
296
|
|
|
* Get index of a value. |
|
297
|
|
|
* |
|
298
|
|
|
* Given a value, this method will return the index of the first occurrence of that value. |
|
299
|
|
|
* |
|
300
|
|
|
* @param mixed $value Value to get the index of |
|
301
|
|
|
* |
|
302
|
|
|
* @return int|null|string |
|
303
|
|
|
*/ |
|
304
|
|
|
public function indexOf($value) |
|
305
|
|
|
{ |
|
306
|
|
|
return $this->fold(function($carry, $val, $key) use ($value) { |
|
307
|
|
|
if (is_null($carry) && $val == $value) { |
|
308
|
|
|
return $key; |
|
309
|
|
|
} |
|
310
|
|
|
return $carry; |
|
311
|
|
|
}); |
|
312
|
|
|
} |
|
313
|
|
|
|
|
314
|
|
|
/** |
|
315
|
|
|
* Get this collection's keys as a collection. |
|
316
|
|
|
* |
|
317
|
|
|
* @return Collection Containing this collection's keys |
|
318
|
|
|
*/ |
|
319
|
|
|
public function keys() |
|
320
|
|
|
{ |
|
321
|
|
|
return static::factory(array_keys($this->getData())); |
|
322
|
|
|
} |
|
323
|
|
|
|
|
324
|
|
|
/** |
|
325
|
|
|
* Get this collection's values as a collection. |
|
326
|
|
|
* |
|
327
|
|
|
* This method returns this collection's values but completely re-indexed (numerically). |
|
328
|
|
|
* |
|
329
|
|
|
* @return Collection Containing this collection's values |
|
330
|
|
|
*/ |
|
331
|
|
|
public function values() |
|
332
|
|
|
{ |
|
333
|
|
|
return static::factory(array_values($this->getData())); |
|
334
|
|
|
} |
|
335
|
|
|
|
|
336
|
|
|
/** |
|
337
|
|
|
* Pad collection to a certain size. |
|
338
|
|
|
* |
|
339
|
|
|
* Returns a new collection, padded to the given size, with the given value. |
|
340
|
|
|
* |
|
341
|
|
|
* @param int $size The number of items that should be in the collection |
|
342
|
|
|
* @param mixed $with The value to pad the collection with |
|
343
|
|
|
* |
|
344
|
|
|
* @return Collection A new collection padded to specified length |
|
345
|
|
|
*/ |
|
346
|
|
|
public function pad($size, $with = null) |
|
347
|
|
|
{ |
|
348
|
|
|
$this->data = array_pad($this->data, $size, $with); |
|
349
|
|
|
|
|
350
|
|
|
return $this; |
|
351
|
|
|
} |
|
352
|
|
|
|
|
353
|
|
|
/** |
|
354
|
|
|
* Apply a callback to each item in collection. |
|
355
|
|
|
* |
|
356
|
|
|
* Applies a callback to each item in collection and returns a new collection |
|
357
|
|
|
* containing each iteration's return value. |
|
358
|
|
|
* |
|
359
|
|
|
* @param callable $mapper The callback to apply |
|
360
|
|
|
* |
|
361
|
|
|
* @return Collection A new collection with callback return values |
|
362
|
|
|
*/ |
|
363
|
|
|
public function map(callable $mapper) |
|
364
|
|
|
{ |
|
365
|
|
|
$iter = 0; |
|
366
|
|
|
$transform = []; |
|
367
|
|
|
foreach ($this as $key => $val) { |
|
368
|
|
|
$transform[$key] = $mapper($val, $key, $iter++); |
|
369
|
|
|
} |
|
370
|
|
|
return static::factory($transform); |
|
371
|
|
|
} |
|
372
|
|
|
|
|
373
|
|
|
/** |
|
374
|
|
|
* Like map, except done in-place. |
|
375
|
|
|
* |
|
376
|
|
|
* @param callable $transformer A transformer callback |
|
377
|
|
|
* |
|
378
|
|
|
* @return $this |
|
379
|
|
|
*/ |
|
380
|
|
|
public function transform(callable $transformer) |
|
381
|
|
|
{ |
|
382
|
|
|
$this->data = $this->map($transformer); |
|
|
|
|
|
|
383
|
|
|
|
|
384
|
|
|
return $this; |
|
385
|
|
|
} |
|
386
|
|
|
|
|
387
|
|
|
/** |
|
388
|
|
|
* Iterate over each item in the collection, calling $callback on it. Return false to stop iterating. |
|
389
|
|
|
* |
|
390
|
|
|
* @param callable $callback A callback to use |
|
391
|
|
|
* |
|
392
|
|
|
* @return $this |
|
393
|
|
|
*/ |
|
394
|
|
|
public function each(callable $callback) |
|
395
|
|
|
{ |
|
396
|
|
|
$iter = 0; |
|
397
|
|
|
foreach ($this as $key => $val) { |
|
398
|
|
|
if (!$callback($val, $key, $iter++)) { |
|
399
|
|
|
break; |
|
400
|
|
|
} |
|
401
|
|
|
} |
|
402
|
|
|
|
|
403
|
|
|
return $this; |
|
404
|
|
|
} |
|
405
|
|
|
|
|
406
|
|
|
/** |
|
407
|
|
|
* Filter the collection. |
|
408
|
|
|
* |
|
409
|
|
|
* Using a callback function, this method will filter out unwanted values, returning |
|
410
|
|
|
* a new collection containing only the values that weren't filtered. |
|
411
|
|
|
* |
|
412
|
|
|
* @param callable $predicate The callback function used to filter |
|
|
|
|
|
|
413
|
|
|
* |
|
414
|
|
|
* @return Collection A new collection with only values that weren't filtered |
|
415
|
|
|
*/ |
|
416
|
|
|
public function filter(callable $predicate = null) |
|
417
|
|
|
{ |
|
418
|
|
|
if (is_null($predicate)) { |
|
419
|
|
|
$filtered = array_filter($this->data); |
|
420
|
|
|
} else { |
|
421
|
|
|
$iter = 0; |
|
422
|
|
|
$filtered = []; |
|
423
|
|
|
foreach($this as $key => $val) { |
|
424
|
|
|
if ($predicate($val, $key, $iter++)) { |
|
425
|
|
|
$filtered[$key] = $val; |
|
426
|
|
|
} |
|
427
|
|
|
} |
|
428
|
|
|
} |
|
429
|
|
|
return static::factory($filtered); |
|
430
|
|
|
} |
|
431
|
|
|
|
|
432
|
|
|
/** |
|
433
|
|
|
* Filter the collection. |
|
434
|
|
|
* |
|
435
|
|
|
* Using a callback function, this method will filter out unwanted values, returning |
|
436
|
|
|
* a new collection containing only the values that weren't filtered. |
|
437
|
|
|
* |
|
438
|
|
|
* @param callable $predicate The callback function used to filter |
|
|
|
|
|
|
439
|
|
|
* |
|
440
|
|
|
* @return Collection A new collection with only values that weren't filtered |
|
441
|
|
|
*/ |
|
442
|
|
|
public function exclude(callable $predicate = null) |
|
443
|
|
|
{ |
|
444
|
|
|
if (is_null($predicate)) { |
|
445
|
|
|
$predicate = function($val) { |
|
446
|
|
|
return (bool) $val != false; |
|
|
|
|
|
|
447
|
|
|
}; |
|
448
|
|
|
} |
|
449
|
|
|
$iter = 0; |
|
450
|
|
|
$excluded = []; |
|
451
|
|
|
foreach ($this as $key => $val) { |
|
452
|
|
|
if (!$predicate($val, $key, $iter++)) { |
|
453
|
|
|
$excluded[$key] = $val; |
|
454
|
|
|
} |
|
455
|
|
|
} |
|
456
|
|
|
return static::factory($excluded); |
|
457
|
|
|
} |
|
458
|
|
|
|
|
459
|
|
|
/** |
|
460
|
|
|
* Return the first item that meets given criteria. |
|
461
|
|
|
* |
|
462
|
|
|
* Using a callback function, this method will return the first item in the collection |
|
463
|
|
|
* that causes the callback function to return true. |
|
464
|
|
|
* |
|
465
|
|
|
* @param callable|null $predicate The callback function |
|
466
|
|
|
* @param mixed|null $default The default return value |
|
467
|
|
|
* |
|
468
|
|
|
* @return mixed |
|
469
|
|
|
*/ |
|
470
|
|
|
public function first(callable $predicate = null, $default = null) |
|
471
|
|
|
{ |
|
472
|
|
|
if (is_null($predicate)) { |
|
473
|
|
|
if ($this->hasOffset(0)) { |
|
474
|
|
|
return $this->getOffset(0); |
|
475
|
|
|
} |
|
476
|
|
|
} else { |
|
477
|
|
|
foreach($this as $index => $value) { |
|
478
|
|
|
if ($predicate($value, $index)) { |
|
479
|
|
|
return $value; |
|
480
|
|
|
} |
|
481
|
|
|
} |
|
482
|
|
|
} |
|
483
|
|
|
|
|
484
|
|
|
return $default; |
|
485
|
|
|
} |
|
486
|
|
|
|
|
487
|
|
|
/** |
|
488
|
|
|
* Return the last item that meets given criteria. |
|
489
|
|
|
* |
|
490
|
|
|
* Using a callback func`tion, this method will return the last item in the collection |
|
491
|
|
|
* that causes the callback function to return true. |
|
492
|
|
|
* |
|
493
|
|
|
* @param callable|null $callback The callback function |
|
494
|
|
|
* @param mixed|null $default The default return value |
|
495
|
|
|
* |
|
496
|
|
|
* @return mixed |
|
497
|
|
|
*/ |
|
498
|
|
|
public function last(callable $callback = null, $default = null) |
|
499
|
|
|
{ |
|
500
|
|
|
$reverse = $this->reverse(); |
|
501
|
|
|
// if (is_null($callback)) { |
|
|
|
|
|
|
502
|
|
|
// return $reverse->getOffset(0); |
|
503
|
|
|
// } |
|
504
|
|
|
return $reverse->first($callback, $default); |
|
505
|
|
|
} |
|
506
|
|
|
|
|
507
|
|
|
/** |
|
508
|
|
|
* Returns collection in reverse order. |
|
509
|
|
|
* |
|
510
|
|
|
* @return Collection This collection in reverse order. |
|
511
|
|
|
*/ |
|
512
|
|
|
public function reverse() |
|
513
|
|
|
{ |
|
514
|
|
|
return static::factory(array_reverse($this->getData(), true)); |
|
515
|
|
|
} |
|
516
|
|
|
|
|
517
|
|
|
/** |
|
518
|
|
|
* Get unique items. |
|
519
|
|
|
* |
|
520
|
|
|
* Returns a collection of all the unique items in this collection. |
|
521
|
|
|
* |
|
522
|
|
|
* @return Collection This collection with duplicate items removed |
|
523
|
|
|
*/ |
|
524
|
|
|
public function unique() |
|
525
|
|
|
{ |
|
526
|
|
|
return static::factory(array_unique($this->getData())); |
|
527
|
|
|
} |
|
528
|
|
|
|
|
529
|
|
|
/** |
|
530
|
|
|
* Collection factory method. |
|
531
|
|
|
* |
|
532
|
|
|
* This method will analyze input data and determine the most appropriate Collection |
|
533
|
|
|
* class to use. It will then instantiate said Collection class with the given |
|
534
|
|
|
* data and return it. |
|
535
|
|
|
* |
|
536
|
|
|
* @param mixed $data The data to wrap |
|
537
|
|
|
* |
|
538
|
|
|
* @return Collection A collection containing $data |
|
539
|
|
|
*/ |
|
540
|
|
|
public static function factory($data = null) |
|
541
|
|
|
{ |
|
542
|
|
|
return new Collection($data); |
|
543
|
|
|
} |
|
544
|
|
|
|
|
545
|
|
|
/** |
|
546
|
|
|
* Determine if structure contains all numeric values. |
|
547
|
|
|
* |
|
548
|
|
|
* @return bool |
|
549
|
|
|
*/ |
|
550
|
|
|
public function isNumeric() |
|
551
|
|
|
{ |
|
552
|
|
|
foreach ($this as $val) { |
|
553
|
|
|
if (!is_numeric($val)) { |
|
554
|
|
|
return false; |
|
555
|
|
|
} |
|
556
|
|
|
} |
|
557
|
|
|
return true; |
|
558
|
|
|
} |
|
559
|
|
|
|
|
560
|
|
|
/** |
|
561
|
|
|
* @inheritdoc |
|
562
|
|
|
*/ |
|
563
|
|
|
public function hasOffset($offset) |
|
564
|
|
|
{ |
|
565
|
|
|
try { |
|
566
|
|
|
$this->getOffsetKey($offset); |
|
567
|
|
|
return true; |
|
568
|
|
|
} catch (OutOfBoundsException $e) { |
|
569
|
|
|
return false; |
|
570
|
|
|
} |
|
571
|
|
|
} |
|
572
|
|
|
|
|
573
|
|
|
/** |
|
574
|
|
|
* @inheritdoc |
|
575
|
|
|
*/ |
|
576
|
|
|
public function getOffsetKey($offset) |
|
577
|
|
|
{ |
|
578
|
|
|
$offset = normalize_offset($offset, $this); |
|
|
|
|
|
|
579
|
|
|
if (!is_null($key = $this->fold(function($carry, $val, $key, $iter) use ($offset) { |
|
580
|
|
|
if ($iter++ === $offset) { |
|
581
|
|
|
return $key; |
|
582
|
|
|
} |
|
583
|
|
|
return $carry; |
|
584
|
|
|
}))) { |
|
585
|
|
|
return $key; |
|
586
|
|
|
} |
|
587
|
|
|
throw new OutOfBoundsException("Offset does not exist: $offset"); |
|
588
|
|
|
} |
|
589
|
|
|
|
|
590
|
|
|
/** |
|
591
|
|
|
* @inheritdoc |
|
592
|
|
|
*/ |
|
593
|
|
|
public function getOffset($offset) |
|
594
|
|
|
{ |
|
595
|
|
|
return $this->retrieve($this->getOffsetKey($offset)); |
|
596
|
|
|
} |
|
597
|
|
|
|
|
598
|
|
|
/** |
|
599
|
|
|
* Get each key/value as an array pair. |
|
600
|
|
|
* |
|
601
|
|
|
* Returns a collection of arrays where each item in the collection is [key,value] |
|
602
|
|
|
* |
|
603
|
|
|
* @return Collection |
|
604
|
|
|
*/ |
|
605
|
|
|
public function pairs() |
|
606
|
|
|
{ |
|
607
|
|
|
return static::factory(array_map( |
|
608
|
|
|
function ($key, $val) { |
|
609
|
|
|
return [$key, $val]; |
|
610
|
|
|
}, |
|
611
|
|
|
array_keys($this->getData()), |
|
612
|
|
|
array_values($this->getData()) |
|
613
|
|
|
)); |
|
614
|
|
|
} |
|
615
|
|
|
|
|
616
|
|
|
// END Iterator methods |
|
617
|
|
|
|
|
618
|
|
|
/** |
|
619
|
|
|
* Counts how many times each value occurs in a collection. |
|
620
|
|
|
* |
|
621
|
|
|
* Returns a new collection with values as keys and how many times that |
|
622
|
|
|
* value appears in the collection. Works best with scalar values but will |
|
623
|
|
|
* attempt to work on collections of objects as well. |
|
624
|
|
|
* |
|
625
|
|
|
* @return Collection |
|
626
|
|
|
*/ |
|
627
|
|
|
public function frequency() |
|
628
|
|
|
{ |
|
629
|
|
|
return static::factory($this->fold( |
|
630
|
|
|
function($carry, $val, $key, $iter) { |
|
|
|
|
|
|
631
|
|
|
if (!isset($carry[$val])) { |
|
632
|
|
|
$carry[$val] = 0; |
|
633
|
|
|
} |
|
634
|
|
|
$carry[$val]++; |
|
635
|
|
|
return $carry; |
|
636
|
|
|
} |
|
637
|
|
|
), []); |
|
|
|
|
|
|
638
|
|
|
} |
|
639
|
|
|
|
|
640
|
|
|
/** |
|
641
|
|
|
* @inheritDoc |
|
642
|
|
|
*/ |
|
643
|
|
|
public function add($index, $value) |
|
644
|
|
|
{ |
|
645
|
|
|
if (!$this->has($index)) { |
|
646
|
|
|
return $this->set($index, $value); |
|
647
|
|
|
} |
|
648
|
|
|
return static::factory($this); |
|
649
|
|
|
} |
|
650
|
|
|
|
|
651
|
|
|
/** |
|
652
|
|
|
* @inheritdoc |
|
653
|
|
|
* @todo Maybe read would be a better name for this? |
|
654
|
|
|
*/ |
|
655
|
|
|
public function get($index, $default = null) |
|
656
|
|
|
{ |
|
657
|
|
|
try { |
|
658
|
|
|
return $this->retrieve($index); |
|
659
|
|
|
} catch (OutOfBoundsException $e) { |
|
660
|
|
|
return $default; |
|
661
|
|
|
} |
|
662
|
|
|
} |
|
663
|
|
|
|
|
664
|
|
|
/** |
|
665
|
|
|
* @inheritdoc |
|
666
|
|
|
* @todo Maybe read would be a better name for this? |
|
667
|
|
|
*/ |
|
668
|
|
|
public function retrieve($index) |
|
669
|
|
|
{ |
|
670
|
|
|
if (!$this->has($index)) { |
|
671
|
|
|
throw new OutOfBoundsException(__CLASS__ . ' could not retrieve value at index ' . $index); |
|
672
|
|
|
} |
|
673
|
|
|
return $this->getData()[$index]; |
|
674
|
|
|
} |
|
675
|
|
|
|
|
676
|
|
|
/** |
|
677
|
|
|
* @inheritDoc |
|
678
|
|
|
*/ |
|
679
|
|
|
public function prepend($item) |
|
680
|
|
|
{ |
|
681
|
|
|
$data = $this->getData(); |
|
682
|
|
|
array_unshift($data, $item); |
|
683
|
|
|
|
|
684
|
|
|
return static::factory($data); |
|
685
|
|
|
} |
|
686
|
|
|
|
|
687
|
|
|
/** |
|
688
|
|
|
* @inheritDoc |
|
689
|
|
|
*/ |
|
690
|
|
|
public function append($item) |
|
691
|
|
|
{ |
|
692
|
|
|
$data = $this->getData(); |
|
693
|
|
|
array_push($data, $item); |
|
694
|
|
|
|
|
695
|
|
|
return static::factory($data); |
|
696
|
|
|
} |
|
697
|
|
|
|
|
698
|
|
|
/** |
|
699
|
|
|
* @inheritDoc |
|
700
|
|
|
*/ |
|
701
|
|
|
public function chunk($size) |
|
702
|
|
|
{ |
|
703
|
|
|
return static::factory($this->fold(function($chunks, $val, $key, $iter) use ($size) { |
|
704
|
|
|
if (is_null($chunks)) { |
|
705
|
|
|
$chunks = []; |
|
706
|
|
|
} |
|
707
|
|
|
if ($iter % $size == 0) { |
|
708
|
|
|
// start new chunk |
|
709
|
|
|
array_push($chunks, []); |
|
710
|
|
|
} |
|
711
|
|
|
$chunk = array_pop($chunks); |
|
712
|
|
|
array_push($chunk, $val); |
|
713
|
|
|
array_push($chunks, $chunk); |
|
714
|
|
|
|
|
715
|
|
|
return $chunks; |
|
716
|
|
|
})); |
|
717
|
|
|
} |
|
718
|
|
|
|
|
719
|
|
|
public function combine($values) |
|
720
|
|
|
{ |
|
721
|
|
|
if (!is_traversable($values)) { |
|
722
|
|
|
throw new InvalidArgumentException(sprintf( |
|
723
|
|
|
'Expecting traversable data for %s but got %s.', |
|
724
|
|
|
__METHOD__, |
|
725
|
|
|
typeof($values) |
|
726
|
|
|
)); |
|
727
|
|
|
} |
|
728
|
|
|
return static::factory( |
|
729
|
|
|
array_combine( |
|
730
|
|
|
$this->keys()->toArray(), |
|
731
|
|
|
with(new Collection($values))->values()->toArray() |
|
732
|
|
|
) |
|
733
|
|
|
); |
|
734
|
|
|
} |
|
735
|
|
|
|
|
736
|
|
|
/** |
|
737
|
|
|
* @inheritDoc |
|
738
|
|
|
*/ |
|
739
|
|
|
public function diff($data, callable $equals = null) |
|
740
|
|
|
{ |
|
741
|
|
|
$diff = []; |
|
742
|
|
|
$orig = $this->getData(); |
|
743
|
|
|
$cmpr = to_array($data); |
|
744
|
|
|
|
|
745
|
|
|
if (is_null ($equals)) { |
|
746
|
|
|
$equals = function($a, $b) { |
|
747
|
|
|
return $a == $b; |
|
748
|
|
|
}; |
|
749
|
|
|
} |
|
750
|
|
|
foreach ($orig as $key => $val) { |
|
751
|
|
|
foreach ($cmpr as $cmpKey => $cmpVal) { |
|
752
|
|
|
if (!$equals($val, $cmpVal)) { |
|
753
|
|
|
$diff[$cmpKey] = $cmpVal; |
|
754
|
|
|
} |
|
755
|
|
|
} |
|
756
|
|
|
} |
|
757
|
|
|
return $diff; |
|
758
|
|
|
} |
|
759
|
|
|
|
|
760
|
|
|
/** |
|
761
|
|
|
* @inheritDoc |
|
762
|
|
|
*/ |
|
763
|
|
|
public function diffKeys($data, callable $equals = null) |
|
764
|
|
|
{ |
|
765
|
|
|
return static::factory( |
|
766
|
|
|
array_diff_key( |
|
767
|
|
|
$this->getData(), |
|
768
|
|
|
to_array($data) |
|
769
|
|
|
) |
|
770
|
|
|
); |
|
771
|
|
|
} |
|
772
|
|
|
|
|
773
|
|
|
/** |
|
774
|
|
|
* @inheritDoc |
|
775
|
|
|
*/ |
|
776
|
|
|
public function nth($nth, $offset = null) |
|
777
|
|
|
{ |
|
778
|
|
|
return $this->slice($offset)->filter(function($val, $key, $iter) use ($nth) { |
|
779
|
|
|
return $iter % $nth == 0; |
|
780
|
|
|
}); |
|
781
|
|
|
} |
|
782
|
|
|
|
|
783
|
|
|
/** |
|
784
|
|
|
* @inheritDoc |
|
785
|
|
|
*/ |
|
786
|
|
|
public function except($indexes) |
|
787
|
|
|
{ |
|
788
|
|
|
return $this->diffKeys(static::factory($indexes)->flip()); |
|
789
|
|
|
} |
|
790
|
|
|
|
|
791
|
|
|
/** |
|
792
|
|
|
* @inheritDoc |
|
793
|
|
|
*/ |
|
794
|
|
|
public function flip() |
|
795
|
|
|
{ |
|
796
|
|
|
return static::factory(array_flip($this->getData())); |
|
797
|
|
|
} |
|
798
|
|
|
|
|
799
|
|
|
/** |
|
800
|
|
|
* @inheritDoc |
|
801
|
|
|
*/ |
|
802
|
|
|
public function intersect($data) |
|
803
|
|
|
{ |
|
804
|
|
|
return static::factory( |
|
805
|
|
|
array_intersect( |
|
806
|
|
|
$this->toArray(), |
|
807
|
|
|
collect($data)->toArray() |
|
|
|
|
|
|
808
|
|
|
) |
|
809
|
|
|
); |
|
810
|
|
|
} |
|
811
|
|
|
|
|
812
|
|
|
/** |
|
813
|
|
|
* @inheritDoc |
|
814
|
|
|
*/ |
|
815
|
|
|
public function intersectKeys($data) |
|
816
|
|
|
{ |
|
817
|
|
|
return static::factory( |
|
818
|
|
|
array_intersect_key( |
|
819
|
|
|
$this->toArray(), |
|
820
|
|
|
collect($data)->toArray() |
|
|
|
|
|
|
821
|
|
|
) |
|
822
|
|
|
); |
|
823
|
|
|
} |
|
824
|
|
|
|
|
825
|
|
|
/** |
|
826
|
|
|
* @inheritDoc |
|
827
|
|
|
*/ |
|
828
|
|
|
public function isEmpty(callable $callback = null) |
|
829
|
|
|
{ |
|
830
|
|
|
if (!is_null($callback)) { |
|
831
|
|
|
return $this->all($callback); |
|
832
|
|
|
} |
|
833
|
|
|
return empty($this->getData()); |
|
834
|
|
|
} |
|
835
|
|
|
|
|
836
|
|
|
/** |
|
837
|
|
|
* @inheritDoc |
|
838
|
|
|
*/ |
|
839
|
|
|
public function only($indices) |
|
840
|
|
|
{ |
|
841
|
|
|
return $this->intersectKeys(static::factory($indices)->flip()->toArray()); |
|
842
|
|
|
} |
|
843
|
|
|
|
|
844
|
|
|
/** |
|
845
|
|
|
* @inheritDoc |
|
846
|
|
|
*/ |
|
847
|
|
|
public function pipe(callable $callback) |
|
848
|
|
|
{ |
|
849
|
|
|
return $callback($this); |
|
850
|
|
|
} |
|
851
|
|
|
|
|
852
|
|
|
/** |
|
853
|
|
|
* @inheritDoc |
|
854
|
|
|
*/ |
|
855
|
|
|
public function random($num) |
|
856
|
|
|
{ |
|
857
|
|
|
return $this->shuffle()->slice(0, $num); |
|
858
|
|
|
} |
|
859
|
|
|
|
|
860
|
|
|
/** |
|
861
|
|
|
* @inheritDoc |
|
862
|
|
|
*/ |
|
863
|
|
|
public function indicesOf($value) |
|
864
|
|
|
{ |
|
865
|
|
|
return $this->filter(function($val) use ($value) { |
|
866
|
|
|
return $val == $value; |
|
867
|
|
|
})->map(function($val, $key) { |
|
868
|
|
|
return $key; |
|
869
|
|
|
}); |
|
870
|
|
|
} |
|
871
|
|
|
|
|
872
|
|
|
/** |
|
873
|
|
|
* @inheritDoc |
|
874
|
|
|
*/ |
|
875
|
|
|
public function shuffle() |
|
876
|
|
|
{ |
|
877
|
|
|
return static::factory(shuffle($this->getData())); |
|
|
|
|
|
|
878
|
|
|
} |
|
879
|
|
|
|
|
880
|
|
|
/** |
|
881
|
|
|
* @inheritDoc |
|
882
|
|
|
*/ |
|
883
|
|
|
public function slice($offset, $length = null) |
|
884
|
|
|
{ |
|
885
|
|
|
return static::factory(array_slice($this->getData(), $offset, $length, true)); |
|
886
|
|
|
} |
|
887
|
|
|
|
|
888
|
|
|
/** |
|
889
|
|
|
* @inheritDoc |
|
890
|
|
|
*/ |
|
891
|
|
|
public function split($num) |
|
892
|
|
|
{ |
|
893
|
|
|
$count = $this->count(); |
|
894
|
|
|
$size = (int)($count / $num); |
|
895
|
|
|
$mod = $count % $num; |
|
896
|
|
|
return static::factory($this->fold(function($chunks, $val, $key, $iter) use ($num, $size, $mod) { |
|
|
|
|
|
|
897
|
|
|
$chunk_count = count($chunks); |
|
898
|
|
|
if ($chunk_count <= $mod) { |
|
899
|
|
|
$size++; |
|
900
|
|
|
} |
|
901
|
|
|
if (is_null($chunks)) { |
|
902
|
|
|
// create initial chunks array... |
|
903
|
|
|
$chunks = [[]]; |
|
904
|
|
|
} |
|
905
|
|
|
|
|
906
|
|
|
// grab the most recent chunk |
|
907
|
|
|
$chunk = array_pop($chunks); |
|
908
|
|
|
|
|
909
|
|
|
if (count($chunk) < $size) { |
|
910
|
|
|
// if chunk is less than the expected size, add value to it |
|
911
|
|
|
array_push($chunk, $val); |
|
912
|
|
|
array_push($chunks, $chunk); |
|
913
|
|
|
} else { |
|
914
|
|
|
// otherwise, add the chunk back and add the value to a new chunk |
|
915
|
|
|
array_push($chunks, $chunk); |
|
916
|
|
|
if ($chunk_count <= $num) { |
|
917
|
|
|
$chunk = [$val]; |
|
918
|
|
|
} |
|
919
|
|
|
array_push($chunks, $chunk); |
|
920
|
|
|
} |
|
921
|
|
|
|
|
922
|
|
|
return $chunks; |
|
923
|
|
|
})); |
|
924
|
|
|
} |
|
925
|
|
|
|
|
926
|
|
|
/** |
|
927
|
|
|
* @inheritDoc |
|
928
|
|
|
*/ |
|
929
|
|
|
public function union($data) |
|
930
|
|
|
{ |
|
931
|
|
|
return static::factory( |
|
932
|
|
|
array_merge( |
|
933
|
|
|
$this->getData(), |
|
934
|
|
|
collect($data)->toArray() |
|
|
|
|
|
|
935
|
|
|
) |
|
936
|
|
|
); |
|
937
|
|
|
} |
|
938
|
|
|
|
|
939
|
|
|
/** |
|
940
|
|
|
* @inheritDoc |
|
941
|
|
|
*/ |
|
942
|
|
|
public function zip(...$data) |
|
943
|
|
|
{ |
|
944
|
|
|
/** @var CollectionInterface $args The function arguments TODO: Change this to SequenceInterface when you have that interface. */ |
|
|
|
|
|
|
945
|
|
|
$args = new Sequence(func_get_args()); |
|
946
|
|
|
$args->map(function($val) { |
|
947
|
|
|
if (is_arrayable($val)) { |
|
948
|
|
|
return to_array($val); |
|
949
|
|
|
} else { |
|
950
|
|
|
// @todo throw exception? |
|
951
|
|
|
return []; |
|
952
|
|
|
} |
|
953
|
|
|
}); |
|
954
|
|
|
$args = $args->prepend($this->getData()) |
|
955
|
|
|
->prepend(null); |
|
956
|
|
|
|
|
957
|
|
|
return static::factory( |
|
958
|
|
|
// array_map( |
|
|
|
|
|
|
959
|
|
|
// null, |
|
960
|
|
|
// $this->getData(), |
|
961
|
|
|
// ...$data |
|
962
|
|
|
// ) |
|
963
|
|
|
call_user_func_array( |
|
964
|
|
|
'array_map', |
|
965
|
|
|
$args->toArray() |
|
966
|
|
|
) |
|
967
|
|
|
); |
|
968
|
|
|
} |
|
969
|
|
|
|
|
970
|
|
|
/** |
|
971
|
|
|
* @inheritDoc |
|
972
|
|
|
*/ |
|
973
|
|
|
public function fold(callable $callback, $initial = null) |
|
974
|
|
|
{ |
|
975
|
|
|
$iter = 0; |
|
976
|
|
|
$carry = $initial; |
|
977
|
|
|
foreach ($this as $key => $val) { |
|
978
|
|
|
$carry = $callback($carry, $val, $key, $iter++); |
|
979
|
|
|
} |
|
980
|
|
|
return $carry; |
|
981
|
|
|
} |
|
982
|
|
|
|
|
983
|
|
|
/** |
|
984
|
|
|
* @inheritDoc |
|
985
|
|
|
*/ |
|
986
|
|
|
public function foldl(callable $callback, $initial = null) |
|
987
|
|
|
{ |
|
988
|
|
|
return $this->reverse()->fold($callback, $initial); |
|
989
|
|
|
} |
|
990
|
|
|
|
|
991
|
|
|
/** |
|
992
|
|
|
* @inheritDoc |
|
993
|
|
|
*/ |
|
994
|
|
|
public function all(callable $callback = null) |
|
995
|
|
|
{ |
|
996
|
|
|
if (is_null($callback)) { |
|
997
|
|
|
$callback = function($val) { |
|
998
|
|
|
return (bool) $val; |
|
999
|
|
|
}; |
|
1000
|
|
|
} |
|
1001
|
|
|
return $this->filter($callback)->isEmpty(); |
|
1002
|
|
|
} |
|
1003
|
|
|
|
|
1004
|
|
|
/** |
|
1005
|
|
|
* @inheritDoc |
|
1006
|
|
|
*/ |
|
1007
|
|
|
public function none(callable $callback = null) |
|
1008
|
|
|
{ |
|
1009
|
|
|
if (is_null($callback)) { |
|
1010
|
|
|
$callback = function($val) { |
|
1011
|
|
|
return (bool) $val; |
|
1012
|
|
|
}; |
|
1013
|
|
|
} |
|
1014
|
|
|
return $this->filter($callback)->isEmpty(); |
|
1015
|
|
|
} |
|
1016
|
|
|
|
|
1017
|
|
|
// BEGIN Numeric Collection Methods |
|
1018
|
|
|
// These methods only really work on numeric data. |
|
1019
|
|
|
|
|
1020
|
|
|
/** |
|
1021
|
|
|
* Increment an item. |
|
1022
|
|
|
* |
|
1023
|
|
|
* Increment the item specified by $key by one value. Intended for integers |
|
1024
|
|
|
* but also works (using this term loosely) for letters. Any other data type |
|
1025
|
|
|
* it may modify is unintended behavior at best. |
|
1026
|
|
|
* |
|
1027
|
|
|
* This method modifies its internal data array rather than returning a new |
|
1028
|
|
|
* collection. |
|
1029
|
|
|
* |
|
1030
|
|
|
* @param mixed $index The key of the item you want to increment. |
|
1031
|
|
|
* @param int $interval The interval that $key should be incremented by |
|
1032
|
|
|
* |
|
1033
|
|
|
* @return Collection |
|
1034
|
|
|
*/ |
|
1035
|
|
|
public function increment($index, $interval = 1) |
|
1036
|
|
|
{ |
|
1037
|
|
|
$val = $this->retrieve($index); |
|
1038
|
|
|
$val += $interval; |
|
1039
|
|
|
return $this->set($index, $val); |
|
1040
|
|
|
} |
|
1041
|
|
|
|
|
1042
|
|
|
/** |
|
1043
|
|
|
* Decrement an item. |
|
1044
|
|
|
* |
|
1045
|
|
|
* Frcrement the item specified by $key by one value. Intended for integers. |
|
1046
|
|
|
* Does not work for letters and if it does anything to anything else, it's |
|
1047
|
|
|
* unintended at best. |
|
1048
|
|
|
* |
|
1049
|
|
|
* This method modifies its internal data array rather than returning a new |
|
1050
|
|
|
* collection. |
|
1051
|
|
|
* |
|
1052
|
|
|
* @param mixed $index The key of the item you want to decrement. |
|
1053
|
|
|
* @param int $interval The interval that $key should be decremented by |
|
1054
|
|
|
* |
|
1055
|
|
|
* @return Collection |
|
1056
|
|
|
*/ |
|
1057
|
|
|
public function decrement($index, $interval = 1) |
|
1058
|
|
|
{ |
|
1059
|
|
|
$val = $this->retrieve($index); |
|
1060
|
|
|
$val -= $interval; |
|
1061
|
|
|
return $this->set($index, $val); |
|
1062
|
|
|
} |
|
1063
|
|
|
|
|
1064
|
|
|
/** |
|
1065
|
|
|
* Get the sum. |
|
1066
|
|
|
* |
|
1067
|
|
|
* @return int|float The sum of all values in collection |
|
1068
|
|
|
*/ |
|
1069
|
|
|
public function sum() |
|
1070
|
|
|
{ |
|
1071
|
|
|
return array_sum($this->toArray()); |
|
1072
|
|
|
} |
|
1073
|
|
|
|
|
1074
|
|
|
/** |
|
1075
|
|
|
* Get the average. |
|
1076
|
|
|
* |
|
1077
|
|
|
* @return float|int The average value from the collection |
|
1078
|
|
|
*/ |
|
1079
|
|
|
public function average() |
|
1080
|
|
|
{ |
|
1081
|
|
|
return $this->sum() / $this->count(); |
|
1082
|
|
|
} |
|
1083
|
|
|
|
|
1084
|
|
|
/** |
|
1085
|
|
|
* Get the mode. |
|
1086
|
|
|
* |
|
1087
|
|
|
* @return float|int The mode |
|
1088
|
|
|
*/ |
|
1089
|
|
|
public function mode() |
|
1090
|
|
|
{ |
|
1091
|
|
|
$counts = $this->counts()->toArray(); |
|
1092
|
|
|
arsort($counts); |
|
1093
|
|
|
$mode = key($counts); |
|
1094
|
|
|
|
|
1095
|
|
|
return (strpos($mode, '.')) ? floatval($mode) : intval($mode); |
|
1096
|
|
|
} |
|
1097
|
|
|
|
|
1098
|
|
|
/** |
|
1099
|
|
|
* Get the median value. |
|
1100
|
|
|
* |
|
1101
|
|
|
* @return float|int The median value |
|
1102
|
|
|
*/ |
|
1103
|
|
|
public function median() |
|
1104
|
|
|
{ |
|
1105
|
|
|
$count = $this->count(); |
|
1106
|
|
|
$data = $this->toArray(); |
|
1107
|
|
|
natcasesort($data); |
|
1108
|
|
|
$middle = $count / 2; |
|
1109
|
|
|
$values = array_values($data); |
|
1110
|
|
|
if ($count % 2 == 0) { |
|
1111
|
|
|
// even number, use middle |
|
1112
|
|
|
$low = $values[$middle - 1]; |
|
1113
|
|
|
$high = $values[$middle]; |
|
1114
|
|
|
|
|
1115
|
|
|
return ($low + $high) / 2; |
|
1116
|
|
|
} |
|
1117
|
|
|
// odd number return median |
|
1118
|
|
|
return $values[$middle]; |
|
1119
|
|
|
} |
|
1120
|
|
|
|
|
1121
|
|
|
/** |
|
1122
|
|
|
* Get the maximum value. |
|
1123
|
|
|
* |
|
1124
|
|
|
* @return mixed The maximum |
|
1125
|
|
|
*/ |
|
1126
|
|
|
public function max() |
|
1127
|
|
|
{ |
|
1128
|
|
|
return max($this->getData()); |
|
1129
|
|
|
} |
|
1130
|
|
|
|
|
1131
|
|
|
/** |
|
1132
|
|
|
* Get the minimum value. |
|
1133
|
|
|
* |
|
1134
|
|
|
* @return mixed The minimum |
|
1135
|
|
|
*/ |
|
1136
|
|
|
public function min() |
|
1137
|
|
|
{ |
|
1138
|
|
|
return min($this->getData()); |
|
1139
|
|
|
} |
|
1140
|
|
|
|
|
1141
|
|
|
/** |
|
1142
|
|
|
* Get the number of times each item occurs in the collection. |
|
1143
|
|
|
|
|
1144
|
|
|
* This method will return a NumericCollection where keys are the |
|
1145
|
|
|
* values and values are the number of times that value occurs in |
|
1146
|
|
|
* the original collection. |
|
1147
|
|
|
|
|
1148
|
|
|
* @return Collection |
|
1149
|
|
|
*/ |
|
1150
|
|
|
public function counts() |
|
1151
|
|
|
{ |
|
1152
|
|
|
return static::factory(array_count_values($this->toArray())); |
|
1153
|
|
|
} |
|
1154
|
|
|
|
|
1155
|
|
|
/** |
|
1156
|
|
|
* @param $serialized |
|
1157
|
|
|
*/ |
|
1158
|
|
|
public function unserialize($serialized) |
|
1159
|
|
|
{ |
|
1160
|
|
|
$this->setData(unserialize($serialized)); |
|
1161
|
|
|
} |
|
1162
|
|
|
|
|
1163
|
|
|
} |
|
1164
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.