Test Setup Failed
Pull Request — master (#19)
by Luke
05:23
created

Collection::sum()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 1
nop 0
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * nozavroni/collect
4
 *
5
 * This is a basic utility library for PHP5.6+ with special emphesis on Collections.
6
 *
7
 * @author Luke Visinoni <[email protected]>
8
 * @copyright (c) 2018 Luke Visinoni <[email protected]>
9
 * @license MIT (see LICENSE file)
10
 */
11
namespace Noz\Collection;
12
13
use Countable;
14
use JsonSerializable;
15
use Iterator;
16
use ArrayAccess;
17
use RuntimeException;
18
use Traversable;
19
20
use function Noz\is_traversable,
21
             Noz\to_array,
22
             Noz\to_numeric;
23
24
/**
25
 * Nozavroni Collection
26
 *
27
 * Basically an array wrapper with a bunch of super useful methods for working with its items and/or create new collections from its items.
28
 *
29
 * @note None of the methods in this class have a $preserveKeys param. That is by design. I don't think it's necessary.
30
 *       Instead, keys are ALWAYS preserved and if you want to NOT preserve keys, simply call Collection::values().
31
 *
32
 * @note The signature for callbacks throughout this class, unless otherwise stated, will be:
33
 *       (mixed $value, mixed $key, int $index), where $index will be simply a numeric value starting at zero, that is
34
 *       incremented by one for each successive call to the callback. The other two arguments should be obvious. The
35
 *       expected return value will depend on the method for which it is being used.
36
 */
37
class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable
38
{
39
    /** @var array The items for this collection */
40
    protected $items;
41
42
    /**
43
     * Collection constructor.
44
     * 
45
     * Although most methods in this class are more forgiving and accept anything that is traversable rather than
46
     * strictly an array, the constructor is an exception. It expects an array. If you have an Array-ish object and it 
47
     * is traversable, you may use the factory method instead to generate a collection from it.
48
     *
49
     * @param array $items The items to include in the collection
50 101
     */
51
    public function __construct(array $items = [])
52 101
    {
53 101
        $this->items = $items;
54 101
        $this->rewind();
55
    }
56
57
    /**
58
     * Generate a collection from any iterable
59
     *
60
     * This is the method used internally to generate new collections. This allows for this class to be extended if 
61
     * necessary. This way, the child class will use its own factory method to generate new collections (or otherwise 
62
     * use this one).
63
     *
64
     * @param array|Traversable $items The items to include in the collection
65
     *
66
     * @return Collection
67 38
     */
68
    public static function factory($items = null)
69 38
    {
70
        return new Collection(to_array($items, true));
71
    }
72
73
    /**
74
     * Get collection as an array
75
     *
76
     * @return array
77 51
     */
78
    public function toArray()
79 51
    {
80
        return $this->items;
81
    }
82
83
    /**
84
     * Determine if collection has a given key
85
     *
86
     * @param mixed $key The key to check for
87
     *
88
     * @return bool
89 51
     */
90
    public function has($key)
91 51
    {
92
        return isset($this->items[$key]) || array_key_exists($key, $this->items);
93
    }
94
95
    /**
96
     * Determine if collection has a value at given position
97
     *
98
     * If the $position argument is positive, counting will start at the beginning and start from one (rather than zero).
99
     * If $position is negative, counting will start at the end and work backwards. This is not the same as array
100
     * indexing, as that begins from zero.
101
     *
102
     * @param int $position The numeric position to check for a value at
103
     *
104
     * @return bool
105 2
     */
106
    public function hasValueAt($position)
107
    {
108 2
        try {
109 2
            $this->getKeyAt($position);
110 2
            return true;
111 2
        } catch (RuntimeException $e) {
112
            return false;
113
        }
114
    }
115
116
    /**
117
     * Get key at given position
118
     *
119
     * If the $position argument is positive, counting will start at the beginning and start from one (rather than zero).
120
     * If $position is negative, counting will start at the end and work backwards. If an item exists at the specified
121
     * position, its key will be returned. Otherwise a RuntimeException will be thrown.
122
     *
123
     * @param int $position The numeric position to get a key at
124
     *
125
     * @return mixed
126
     *
127
     * @throws RuntimeException
128 6
     */
129
    public function getKeyAt($position)
130 6
    {
131 6
        $collection = $this;
132 3
        if ($position < 0) {
133 3
            $collection = $this->reverse();
134 6
        }
135 6
        $i = 1;
136 6
        foreach ($collection as $key => $val) {
137 5
            if (abs($position) == $i++) {
138
                return $key;
139 6
            }
140 3
        }
141
        throw new RuntimeException("No key at position {$position}");
142
    }
143
144
    /**
145
     * Get value at given position
146
     *
147
     * If the $position argument is positive, counting will start at the beginning and start from one (rather than zero).
148
     * If $position is negative, counting will start at the end and work backwards. If an item exists at the specified
149
     * position, its value will be returned. Otherwise a RuntimeException will be thrown.
150
     *
151
     * @param int $position The numeric position to get a value at
152
     *
153
     * @return mixed
154
     *
155
     * @throws RuntimeException
156 2
     */
157
    public function getValueAt($position)
158 2
    {
159
        return $this->get($this->getKeyAt($position));
160
    }
161
162
    /**
163
     * Get the key of first item exactly equal to $item
164
     *
165
     * Searches the collection for an item exactly equal to $item, returning its key if found. If a callback is provided
166
     * rather than a value, it will be passed the conventional three arguments ($value, $key, $index) and returning true
167
     * from this callback would be considered a "match". If no match is found, a RuntimeException will be thrown.
168
     *
169
     * @param mixed|callable $item The value to look for or a callback
170
     *
171
     * @throws RuntimeException
172
     *
173
     * @return mixed
174 3
     */
175
    public function keyOf($item)
176 3
    {
177 3
        $index = 0;
178 3
        foreach ($this as $key => $val) {
179 1
            if (is_callable($item)) {
180 1
                if ($item($val, $key, $index++)) {
181
                    return $key;
182 3
                }
183 1
            } elseif ($item === $val) {
184
                return $key;
185 3
            }
186
        }
187 1
188
        throw new RuntimeException("Could not find item equal to '{$item}'");
189
    }
190
191
    /**
192
     * Get the numeric index of first item exactly equal to $item
193
     *
194
     * Searches the collection for an item exactly equal to $item, returning its numeric index if found. If a callback
195
     * is provided rather than a value, it will be passed the conventional three arguments ($value, $key, $index) and
196
     * returning true from this callback would be considered a "match". If no match is found, a RuntimeException will be
197
     * thrown.
198
     *
199
     * @param mixed|callable $item The value to look for or a callback
200
     *
201
     * @throws RuntimeException
202
     *
203
     * @return int
204 3
     */
205
    public function indexOf($item)
206 3
    {
207 3
        $index = 0;
208 3
        foreach ($this as $key => $val) {
209 1
            if (is_callable($item)) {
210 1
                if ($item($val, $key, $index)) {
211
                    return $index;
212 1
                }
213 2
            } else {
214 1
                if ($item === $val) {
215
                    return $index;
216
                }
217 3
            }
218 3
            $index++;
219
        }
220 1
221
        throw new RuntimeException("Could not find item equal to '{$item}'");
222
    }
223
224
    /**
225
     * Get item by key
226
     *
227
     * Fetches an item from the collection by key. If no item is found with the given key, a default may be provided as
228
     * the second argument. If no default is provided, null will be returned instead.
229
     *
230
     * @param mixed $key The key of the item you want returned
231
     * @param mixed $default A default value to return if key does not exist
232
     *
233
     * @return mixed
234 8
     */
235
    public function get($key, $default = null)
236 8
    {
237 8
        if ($this->has($key)) {
238
            return $this->items[$key];
239
        }
240 3
241
        return $default;
242
    }
243
244
    /**
245
     * Add an item with no regard to key
246
     *
247
     * Simply adds a value at the end of the collection. A numeric index will be created automatically.
248
     *
249
     * @param mixed $value The value to add to the collection
250
     *
251
     * @return self
252 8
     */
253
    public function add($value)
254 8
    {
255
        $this->items[] = $value;
256 8
257
        return $this;
258
    }
259
260
    /**
261
     * Assign a value to the given key
262
     *
263
     * Sets the specified key to the specified value. By default the key will be overwritten if it already exists, but
264
     * this behavior may be changed by setting the third parameter ($overwrite) to false.
265
     *
266
     * @param mixed $key The key to assign a value to
267
     * @param mixed $value The value to assign to $key
268
     * @param bool $overwrite Whether to overwrite existing values (default is true)
269
     *
270
     * @return self
271 15
     */
272
    public function set($key, $value, $overwrite = true)
273 15
    {
274 15
        if ($overwrite || !$this->has($key)) {
275 15
            $this->items[$key] = $value;
276
        }
277 15
278
        return $this;
279
    }
280
281
    /**
282
     * Delete an item by key
283
     *
284
     * Remove the item at the given key from the collection.
285
     *
286
     * @param mixed $key The key of the item to remove
287
     *
288
     * @return self
289 3
     */
290
    public function delete($key)
291 3
    {
292
        unset($this->items[$key]);
293 3
294
        return $this;
295
    }
296
297
    /**
298
     * Clear (remove) all items from the collection.
299
     *
300
     * @return self
301 2
     */
302
    public function clear()
303 2
    {
304
        $this->items = [];
305 2
306
        return $this;
307
    }
308
309
    /**
310
     * Determine if collection contains given value
311
     *
312
     * Checks the collection for an item exactly equal to $value. If $value is a callback function, it will be passed
313
     * the typical arguments ($value, $key, $index) and a true return value will count as a match.
314
     *
315
     * If $key argument is provided, key must match it as well. By default key is not required.
316
     *
317
     * @param mixed|callable $value The value to check for or a callback function
318
     * @param mixed $key The key to check for in addition to the value (optional)
319
     *
320
     * @return bool
321 4
     */
322
    public function contains($value, $key = null)
323 4
    {
324 4
        $index = 0;
325 4
        foreach ($this as $k => $v) {
326 4
            $matchkey = is_null($key) || $key === $k;
327 1
            if (is_callable($value)) {
328 1
                if ($value($v, $k, $index)) {
329
                    return $matchkey;
330 1
                }
331 3
            } else {
332 3
                if ($value === $v) {
333
                    return $matchkey;
334
                }
335 4
            }
336 4
            $index++;
337 2
        }
338
        return false;
339
    }
340
341
    /**
342
     * Pull an item out of the collection and return it
343
     *
344
     * @param mixed $key The key whose value should be removed and returned
345
     *
346
     * @return mixed
347 1
     */
348
    public function pull($key)
349 1
    {
350 1
        if ($this->has($key)) {
351 1
            $value = $this->get($key);
352 1
            $this->delete($key);
353
            return $value;
354 1
        }
355
    }
356
357
    /**
358
     * Join collection items using a delimiter
359
     *
360
     * Similar to implode() or join(), this method will attempt to return every item in the collection  delimited
361
     * (separated) by the specified character(s).
362
     *
363
     * @param string $delim The character(s) to delimit (separate) the results with
364
     *
365
     * @return string
366 1
     */
367
    public function join($delim = '')
368 1
    {
369
        return implode($delim, $this->items);
370
    }
371
372
    /**
373
     * Determine if collection is empty (has no items)
374
     *
375
     * @return bool
376 5
     */
377
    public function isEmpty()
378 5
    {
379
        return $this->count() == 0;
380
    }
381
382
    /**
383
     * Get new collection with only values
384
     *
385
     * Return a new collection with only the current collection's values. The keys will be indexed numerically from zero
386
     *
387
     * @return Collection
388 2
     */
389
    public function values()
390 2
    {
391
        return static::factory(array_values($this->items));
392
    }
393
394
    /**
395
     * Get new collection with only keys
396
     *
397
     * Return a new collection with only the current collection's keys as its values.
398
     *
399
     * @return Collection
400 1
     */
401
    public function keys()
402 1
    {
403
        return static::factory(array_keys($this->items));
404
    }
405
406
    /**
407
     * Get a collection of key/value pairs
408
     *
409
     * Returns a new collection containing arrays of key/value pairs in the format [key, value].
410
     *
411
     * @return Collection
412 1
     */
413
    public function pairs()
414
    {
415 1
        return $this->map(function($val, $key) {
416 1
            return [$key, $val];
417
        })->values();
418
    }
419
420
    /**
421
     * Get a collection with order reversed
422
     *
423
     * @return Collection
424 6
     */
425
    public function reverse()
426 6
    {
427
        return static::factory(array_reverse($this->items));
428
    }
429
430
    /**
431
     * Get a collection with keys and values flipped.
432
     *
433
     * Returns a new collection containing the keys as values and the values as keys.
434
     *
435
     * @return Collection
436 1
     */
437
    public function flip()
438 1
    {
439 1
        $collection = static::factory();
440 1
        foreach ($this as $key => $val) {
441 1
            $collection->set($val, $key);
442 1
        }
443
        return $collection;
444
    }
445
446
    /**
447
     * Shuffle (randomize) the order of this collection's values (in-place)
448
     *
449
     * @return Collection
450 1
     */
451
    public function shuffle()
452 1
    {
453 1
        shuffle($this->items);
454
        return $this;
455
    }
456
457
    /**
458
     * Get a random value from the collection
459
     * 
460
     * @return mixed
461 1
     */
462
    public function random()
463 1
    {
464
        return $this->getValueAt(rand(1, $this->count()));
465
    }
466
467
    /**
468
     * Sort the collection by value (in-place)
469
     *
470
     * Sorts the collection by value using the provided algorithm (which can be either the name of a native php function
471
     * or a callable).
472
     *
473
     * @note The sorting methods are exceptions to the usual callback signature. The callback for this method accepts
474
     *       the standard arguments for sorting algorithms ( string $str1 , string $str2 ) and should return an integer.
475
     *
476
     * @see http://php.net/manual/en/function.strcmp.php
477
     *
478
     * @param callable $alg The sorting algorithm (defaults to strcmp)
479
     *
480
     * @return self
481 2
     */
482
    public function sort(callable $alg = null)
483 2
    {
484
        if (is_null($alg)) {
485 1
            // case-sensitive string comparison is the default sorting mechanism
486 1
            $alg = 'strcmp';
487 2
        }
488
        uasort($this->items, $alg);
489 2
490
        return $this;
491
    }
492
493
    /**
494
     * Sort the collection by key (in-place)
495
     *
496
     * Sorts the collection by key using the provided algorithm (which can be either the name of a native php function
497
     * or a callable).
498
     *
499
     * @note The sorting methods are exceptions to the usual callback signature. The callback for this method accepts
500
     *       the standard arguments for sorting algorithms ( string $str1 , string $str2 ) and should return an integer.
501
     *
502
     * @see http://php.net/manual/en/function.strcmp.php
503
     *
504
     * @param callable $alg The sorting algorithm (defaults to strcmp)
505
     *
506
     * @return self
507 2
     */
508
    public function ksort(callable $alg = null)
509 2
    {
510
        if (is_null($alg)) {
511 1
            // case-sensitive string comparison is the default sorting mechanism
512 1
            $alg = 'strcmp';
513 2
        }
514
        uksort($this->items, $alg);
515 2
516
        return $this;
517
    }
518
519
    /**
520
     * Append items to collection without regard to key
521
     *
522
     * Much like Collection::add(), except that it accepts multiple items to append rather than just one.
523
     *
524
     * @param array|Traversable $items A list of values to append to the collection
525
     *
526
     * @return self
527 2
     */
528
    public function append($items)
529 2
    {
530 1
        if (!is_traversable($items)) {
531
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
532
        }
533 1
534 1
        foreach ($items as $val) {
535 1
            $this->add($val);
536
        }
537 1
538
        return $this;
539
    }
540
541
    /**
542
     * Return first item or first item where callback returns true
543
     *
544
     * Returns the first item in the collection. If a callback is provided, it will accept the standard arguments
545
     * ($value, $key, $index) and returning true will be considered a "match".
546
     *
547
     * @param callable|null $callback A callback to compare items with (optional)
548
     *
549
     * @return mixed|null
550 8
     */
551
    public function first(callable $callback = null)
552 8
    {
553 8
        $index = 0;
554 8
        foreach ($this as $key => $val) {
555 8
            if (is_null($callback) || $callback($val, $key, $index++)) {
556
                return $val;
557 6
            }
558
        }
559
560
        return null;
561
    }
562
563
    /**
564
     * Return last item or last item where callback returns true
565
     *
566
     * Returns the last item in the collection. If a callback is provided, it will accept the standard arguments
567
     * ($value, $key, $index) and returning true will be considered a "match".
568
     *
569
     * @param callable|null $callback A callback to compare items with (optional)
570
     *
571
     * @return mixed|null
572 3
     */
573
    public function last(callable $callback = null)
574 3
    {
575
        return $this->reverse()->first($callback);
576
    }
577
578
    /**
579
     * Create a new collection by applying a callback to each item in the collection
580
     *
581
     * The callback for this method should accept the standard arguments ($value, $key, $index). It will be called once
582
     * for every item in the collection and a new collection will be created with the results.
583
     *
584
     * @note It is worth noting that keys will be preserved in the resulting collection, so if you do not want this
585
     *       behavior, simply call values() on the resulting collection and it will be indexed numerically.
586
     *
587
     * @param callable $callback A callback that is applied to every item in the collection
588
     *
589
     * @return Collection
590 4
     */
591
    public function map(callable $callback)
592 4
    {
593
        $collection = static::factory();
594 4
595 4
        $index = 0;
596 4
        foreach ($this as $key => $val) {
597 4
            $collection->set($key, $callback($val, $key, $index++));
598
        }
599 4
600
        return $collection;
601
    }
602
603
    /**
604
     * Combine collection with another collection/array/traversable
605
     *
606
     * Using this collection's keys, and the incoming collection's values, a new collection is created and returned.
607
     *
608
     * @param array|Traversable $items The values to combine with this collection's keys
609
     *
610
     * @return Collection
611 5
     */
612
    public function combine($items)
613 5
    {
614 1
        if (!is_traversable($items)) {
615
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
616
        }
617 4
618 4
        $items = to_array($items);
619 1
        if (count($items) != count($this->items)) {
620
            throw new RuntimeException("Invalid input for " . __METHOD__ . ", number of items does not match");
621
        }
622 3
623
        return static::factory(array_combine($this->items, $items));
624
    }
625
626
    /**
627
     * Get a new collection with only distinct values
628
     *
629
     * @return Collection
630 1
     */
631
    public function distinct()
632 1
    {
633 1
        $collection = static::factory();
634 1
        foreach ($this as $key => $val) {
635 1
            if (!$collection->contains($val)) {
636 1
                $collection->set($key, $val);
637 1
            }
638
        }
639 1
640
        return $collection;
641
    }
642
643
    /**
644
     * Remove all duplicate values from collection (in-place)
645
     *
646
     * @return Collection
647 1
     */
648
    public function deduplicate()
649 1
    {
650
        $this->items = array_unique($this->items);
651 1
652
        return $this;
653
    }
654
655
    /**
656
     * Get frequency of each distinct item in collection
657
     *
658
     * Returns a new collection with each distinct scalar value converted to a string as its keys and the number if
659
     * times it occurs in the collection (its frequency) as its values. Non-scalar values will simply be discarded.
660
     *
661
     * @return Collection
662
     */
663
    public function frequency()
664
    {
665 2
        return $this->fold(function(Collection $freq, $val) {
666
            if (is_scalar($val)) {
667 2
                $str = (string) $val;
668 2
                if (!isset($freq[$str])) {
669 2
                    $freq[$str] = 0;
670 2
                }
671 1
                $freq[$str] += 1;
672 1
            }
673 1
            return $freq;
674 1
        }, new Collection);
675 1
    }
676 1
677 1
    /**
678
     * Get new collection with only filtered values
679 2
     *
680
     * Loops through every item in the collection, applying the given callback and creating a new collection with only
681 2
     * those items which return true from the callback. The callback should accept the standard arguments
682
     * ($value, $key, $index). If no callback is provided, items with "truthy" values will be kept.
683
     *
684
     * @param callable $callback A callback function used to determine which items are kept (optional)
685
     *
686
     * @return Collection
687
     */
688
    public function filter(callable $callback = null)
689
    {
690
        $collection = static::factory();
691
        $index = 0;
692
        foreach ($this as $key => $value) {
693
            if (is_null($callback)) {
694
                if ($value) {
695
                    $collection->set($key, $value);
696 1
                }
697
            } else {
698 1
                if ($callback($value, $key, $index++)) {
699 1
                    $collection->set($key, $value);
700 1
                }
701 1
            }
702 1
        }
703
704 1
        return $collection;
705
    }
706
707
    /**
708
     * Fold collection into a single value (a.k.a. reduce)
709
     *
710
     * Apply a callback function to each item in the collection, passing the result to the next call until only a single
711
     * value remains. The arguments provided to this callback are ($folded, $val, $key, $index) where $folded is the
712
     * result of the previous call (or if the first call it is equal to the $initial param).
713
     *
714
     * @param callable $callback The callback function used to "fold" or "reduce" the collection into a single value
715
     * @param mixed $initial The (optional) initial value to pass to the callback
716
     *
717 3
     * @return mixed
718
     */
719 3
    public function fold(callable $callback, $initial = null)
720 1
    {
721
        $index = 0;
722
        $folded = $initial;
723 2
        foreach ($this as $key => $val) {
724 2
            $folded = $callback($folded, $val, $key, $index++);
725 2
        }
726 2
727
        return $folded;
728 2
    }
729
730
    /**
731
     * Return a merge of this collection and $items
732
     *
733
     * Returns a new collection with a merge of this collection and $items. Values from $items will overwrite values in
734
     * the current collection.
735
     *
736
     * @param array|Traversable $items The items to merge with the collection
737
     *
738 2
     * @return Collection
739
     */
740 2
    public function merge($items)
741 1
    {
742
        if (!is_traversable($items)) {
743
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
744 1
        }
745 1
746 1
        $collection = clone $this;
747 1
        foreach ($items as $key => $val) {
748
            $collection->set($key, $val);
749 1
        }
750
751
        return $collection;
752
    }
753
754
    /**
755
     * Create a new collection with a union of this collection and $items
756
     *
757
     * This method is similar to merge, except that existing values will not be overwritten.
758
     *
759
     * @param $items
760
     */
761 2
    public function union($items)
762
    {
763 2
        if (!is_traversable($items)) {
764 2
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
765 2
        }
766 1
767
        $collection = clone $this;
768 2
        foreach ($items as $key => $val) {
769
            $collection->set($key, $val, false);
770 2
        }
771
772
        return $collection;
773
    }
774
775
    /**
776
     * Apply a callback function to each item in the collection passively
777
     *
778
     * To stop looping through the items in the collection, return false from the callback.
779
     *
780
     * @param callable $callback The callback to use on each item in the collection
781
     *
782
     * @return self
783
     */
784 2
    public function each(callable $callback)
785
    {
786 2
        $index = 0;
787 2
        foreach ($this as $key => $val) {
788 2
            if ($callback($val, $key, $index++) === false) {
789 2
                break;
790
            }
791 2
        }
792
793 2
        return $this;
794
    }
795
796
    /**
797
     * Assert callback returns $expected value for each item in collection.
798
     *
799
     * This method will loop over each item in the collection, passing them to the callback. If the callback doesn't
800
     * return $expected value for every item in the collection, it will return false.
801
     *
802
     * @param callable $callback Assertion callback
803
     * @param bool $expected Expected value from callback
804
     *
805 1
     * @return bool
806
     */
807 1
    public function assert(callable $callback, $expected = true)
808
    {
809
        $index = 0;
810
        foreach ($this as $key => $val) {
811
            if ($callback($val, $key, $index++) !== $expected) {
812
                return false;
813
            }
814
        }
815
816
        return true;
817
    }
818
819 2
    /**
820
     * Pipe collection through a callback
821 2
     *
822
     * Simply passes the collection as an argument to the given callback.
823
     *
824
     * @param callable $callback The callback function (passed only one arg, the collection itself)
825
     *
826
     * @return mixed
827
     */
828
    public function pipe(callable $callback)
829
    {
830
        return $callback($this);
831
    }
832
833 1
    /**
834
     * Get new collection in chunks of $size
835 1
     *
836
     * Creates a new collection of arrays of $size length. The remainder items will be placed at the end.
837
     *
838
     * @param int $size The size of the arrays you want returned
839
     *
840
     * @return Collection
841
     */
842
    public function chunk($size)
843
    {
844
        return static::factory(array_chunk($this->items, $size, true));
845
    }
846
847
    /**
848 1
     * Get a new collection of $count chunks
849
     *
850 1
     * Returns a collection of $count number of equally-sized arrays, placing remainders at the end.
851
     *
852
     * @param int $count The number of arrays you want returned
853
     *
854
     * @return Collection
855
     */
856
    public function split($count = 1)
857
    {
858
        return $this->chunk(ceil($this->count() / $count));
859
    }
860
861
    /**
862 1
     * Get a slice of this collection.
863
     *
864 1
     * Returns a collection with a slice of this collection's items, starting at $offset and continuing until $length
865
     *
866
     * @param int $offset The offset at which you want the slice to begin
867
     * @param int|null $length The length of the slice (number of items)
868
     *
869
     * @return Collection
870
     */
871
    public function slice($offset, $length = null)
872
    {
873
        return static::factory(array_slice($this->items, $offset, $length, true));
874
    }
875
876 2
    /**
877
     * Get collection with only differing items
878 2
     *
879
     * Returns a collection containing only the items not present in *both* this collection and $items.
880
     *
881
     * @param array|Traversable $items The items to compare with
882
     *
883
     * @return Collection
884
     */
885
    public function diff($items)
886
    {
887
        return static::factory(array_diff($this->items, to_array($items)));
888
    }
889
890 1
    /**
891
     * Get collection with only differing items (by key)
892 1
     *
893
     * Returns a collection containing only the values whose keys are not present in *both* this collection and $items.
894
     *
895
     * @param array|Traversable $items The items to compare with
896
     *
897
     * @return Collection
898
     */
899
    public function kdiff($items)
900
    {
901
        return static::factory(array_diff_key($this->items, to_array($items)));
902
    }
903
904 1
    /**
905
     * Get collection with only intersecting items
906 1
     *
907
     * Returns a collection containing only the values present in *both* this collection and $items
908
     *
909
     * @param array|Traversable $items The items to compare with
910
     *
911
     * @return Collection
912
     */
913
    public function intersect($items)
914 1
    {
915
        return static::factory(array_intersect($this->items, to_array($items)));
916 1
    }
917
918
    /**
919
     * Get collection with only intersecting items (by key)
920
     *
921
     * Returns a collection containing only the values whose keys are present in *both* this collection and $items
922
     *
923
     * @param array|Traversable $items The items to compare with
924
     *
925
     * @return Collection
926 2
     */
927
    public function kintersect($items)
928 2
    {
929
        return static::factory(array_intersect_key($this->items, to_array($items)));
930
    }
931
932
    /**
933
     * Remove last item in collection and return it
934
     *
935
     * @return mixed
936
     */
937
    public function pop()
938
    {
939
        return array_pop($this->items);
940 1
    }
941
942 1
    /**
943
     * Remove first item in collection and return it
944
     *
945
     * If the collection is numerically indexed, this method will re-index it from 0 after returning the item.
946
     *
947
     * @return mixed
948
     */
949
    public function shift()
950
    {
951
        return array_shift($this->items);
952
    }
953
954 5
    /**
955
     * Add item to the end of the collection
956 5
     *
957
     * @note This method is no different than add() but I included it for consistency's sake since I have the others
958 5
     *
959
     * @param mixed $item The item to add to the collection
960
     *
961
     * @return self
962
     */
963
    public function push($item)
964
    {
965
        return $this->add($item);
966
    }
967
968
    /**
969
     * Add item to the beginning of the collection
970
     *
971
     * The collection will be re-indexed if it has numeric keys.
972
     *
973 3
     * @param mixed $item The item to add to the collection
974
     *
975 3
     * @return self
976 3
     */
977 3
    public function unshift($item)
978 3
    {
979 3
        array_unshift($this->items, $item);
980 3
981
        return $this;
982 3
    }
983
984 3
    /**
985
     * Get new collection padded to specified $size with $value
986
     *
987
     * Using $value, pad the collection to specified $size. If $size is smaller or equal to the size of the collection,
988
     * then no padding takes place. If $size is positive, padding is added to the end, while if negative, padding will
989
     * be added to the beginning.
990
     *
991
     * @param int $size The number of items collection should have
992
     * @param mixed $value The value to pad with
993
     *
994
     * @return Collection
995
     */
996
    public function pad($size, $value = null)
997
    {
998 2
        $collection = clone $this;
999
        while ($collection->count() < abs($size)) {
1000 2
            if ($size > 0) {
1001 2
                $collection->add($value);
1002
            } else {
1003 2
                $collection->unshift($value);
1004 2
            }
1005 1
        }
1006 1
1007 1
        return $collection;
1008 1
    }
1009
1010 2
    /**
1011
     * Partition collection into two collections using a callback
1012 2
     *
1013
     * Iterates over each element in the collection with a callback. Items where callback returns true are placed in one
1014
     * collection and the rest in another. Finally, the two collections are placed in an array and returned for easy use
1015
     * with the list() function. ( `list($a, $b) = $col->partition(function($val, $key, $index) {})` )
1016
     *
1017
     * @param callable $callback The comparison callback
1018
     *
1019
     * @return Collection[]
1020
     */
1021
    public function partition(callable $callback)
1022
    {
1023
        $pass = static::factory();
1024
        $fail = static::factory();
1025
1026 3
        $index = 0;
1027
        foreach ($this as $key => $val) {
1028 3
            if ($callback($val, $key, $index++)) {
1029
                $pass->set($key, $val);
1030
            } else {
1031
                $fail->set($key, $val);
1032
            }
1033
        }
1034
1035
        return [$pass, $fail];
1036
    }
1037
1038
    /**
1039 1
     * Get sum of all numeric items
1040
     *
1041 1
     * Returns the sum of all numeric items in the collection, silently ignoring any non-numeric values.
1042 1
     *
1043 1
     * @return float|int
1044 1
     */
1045
    public function sum()
1046 1
    {
1047 1
        return $this->fold(function($accum, $val) {
1048 1
            return is_numeric($val) ? $accum + $val : $accum;
1049 1
        }, 0);
1050
    }
1051
1052
    /**
1053
     * Get product of all numeric items
1054
     *
1055
     * Returns the product of all numeric items in the collection, silently ignoring any non-numeric values.
1056
     *
1057
     * @return float|int
1058
     */
1059
    public function product()
1060
    {
1061
        if ($this->isEmpty()) {
1062
            return 0;
1063 1
        }
1064
        return $this->fold(function($accum, $val) {
1065 1
            return is_numeric($val) ? $accum * $val : $accum;
1066
        }, 1);
1067
    }
1068
1069
    /**
1070
     * Get average of all numeric items
1071
     *
1072
     * Returns the average of all numeric items in the collection, silently ignoring any non-numeric values.
1073
     *
1074
     * @return float|int
1075
     */
1076
    public function average()
1077
    {
1078
        $numeric = $this->filter('Noz\is_numeric');
1079
        if (!$count = $numeric->count()) {
1080
            return 0;
1081 1
        }
1082
        return $numeric->sum() / $count;
1083 1
    }
1084
1085
    /**
1086
     * Get the median numeric value
1087
     *
1088
     * Returns the median of all numeric items in the collection, silently ignoring any non-numeric values.
1089
     *
1090
     * @return float|int
1091
     */
1092
    public function median()
1093
    {
1094
        $numeric = $this->filter('Noz\is_numeric')->sort();
1095 2
        if (!$count = $numeric->count()) {
1096
            return 0;
1097 2
        }
1098 1
        $pos = ($count + 1) / 2;
1099
        if (!is_int($pos)) {
1100
            return ($numeric->getValueAt(floor($pos)) + $numeric->getValueAt(ceil($pos))) / 2;
1101 1
        }
1102
        return to_numeric($numeric->getValueAt($pos));
1103
    }
1104
1105
    /**
1106
     * Get the mode numeric value
1107
     *
1108
     * Returns the mode of all numeric items in the collection, silently ignoring any non-numeric values.
1109
     *
1110
     * @return float|int
1111
     */
1112
    public function mode()
1113 1
    {
1114
        $mode = $this->filter('Noz\is_numeric')
1115 1
            ->frequency()
1116 1
            ->sort()
1117
            ->keys()
1118
            ->pop();
1119
1120
        return to_numeric($mode);
1121
    }
1122
1123
    /**
1124
     * Get maximum numeric value from collection
1125
     *
1126
     * Returns the max of all numeric items in the collection, silently ignoring any non-numeric values.
1127
     *
1128 1
     * @return float|int
1129
     */
1130 1
    public function max()
1131 1
    {
1132 1
        return to_numeric(max($this->items));
1133
    }
1134 1
1135 1
    /**
1136
     * Get minimum numeric value from collection
1137
     *
1138
     * Returns the min of all numeric items in the collection, silently ignoring any non-numeric values.
1139
     *
1140
     * @return float|int
1141
     */
1142
    public function min()
1143
    {
1144 39
        return to_numeric(min($this->items));
1145
    }
1146 39
1147
    /**
1148
     * Get column values by key
1149
     *
1150
     * This method expects the collection's data to be tabular in nature (two-dimensional and for the rows to have
1151
     * consistently named keys). If the data is not structured this way, it will do the best it can but it is not meant
1152 39
     * for unstructured, non-tabular data so don't expect consistent results.
1153
     *
1154 39
     * @param string|int $column The key of the column you want to get
1155
     *
1156
     * @return Collection
1157
     */
1158
    public function getColumn($column)
1159
    {
1160 38
        return static::factory(array_column($this->items, $column));
1161
    }
1162 38
1163
    /**
1164
     * Is collection tabular?
1165
     *
1166
     * Returns true if the data in the collection is tabular in nature, meaning it is at least two-dimensional and each
1167
     * row contains the same number of values with the same keys.
1168 101
     *
1169
     * @return bool
1170 101
     */
1171 101
    public function isTabular()
1172
    {
1173
        $first = $this->first();
1174
        return $this->assert(function($row) use ($first) {
1175
            if (!is_traversable(($first)) || !is_traversable($row)) {
1176 40
                return false;
1177
            }
1178 40
            return Collection::factory($row)
1179
                ->kdiff($first)
1180
                ->isEmpty();
1181
        });
1182
    }
1183
1184
    /** ++++                  ++++ **/
1185
    /** ++ Interface Compliance ++ **/
1186
    /** ++++                  ++++ **/
1187
1188
    /**
1189
     * JSON serialize
1190 11
     *
1191
     * @ignore
1192 11
     *
1193
     * @return array
1194
     */
1195
    public function jsonSerialize()
1196
    {
1197
        return $this->toArray();
1198
    }
1199
1200
    /** ++++                  ++++ **/
1201
    /** ++ Array Access Methods ++ **/
1202
    /** ++++                  ++++ **/
1203
1204
    /**
1205
     * Does offset exist?
1206
     *
1207
     * @ignore
1208
     *
1209
     * @param mixed $offset
1210
     *
1211
     * @return bool
1212
     */
1213
    public function offsetExists($offset)
1214
    {
1215
        return $this->has($offset);
1216
    }
1217
1218
    /**
1219
     * Get item at offset
1220
     *
1221
     * @ignore
1222
     *
1223
     * @param mixed $offset
1224
     *
1225
     * @return mixed
1226
     */
1227
    public function offsetGet($offset)
1228
    {
1229
        if (!$this->has($offset)) {
1230
            throw new RuntimeException("Unknown offset: {$offset}");
1231
        }
1232
1233
        return $this->get($offset);
1234
    }
1235
1236
    /**
1237
     * Unset item at offset
1238
     *
1239
     * @ignore
1240
     *
1241
     * @param mixed $offset
1242
     *
1243
     * @return void
1244
     */
1245
    public function offsetUnset($offset)
1246
    {
1247
        $this->delete($offset);
1248
    }
1249
1250
    /**
1251
     * Set item at offset
1252
     *
1253
     * @ignore
1254
     *
1255
     * @param mixed $offset
1256
     * @param mixed $value
1257
     *
1258
     * @return self
1259
     */
1260
    public function offsetSet($offset, $value)
1261
    {
1262
        if (!isset($offset)) {
1263
            $this->add($value);
1264
        }
1265
1266
        $this->set($offset, $value);
1267
    }
1268
1269
    /** ++++                  ++++ **/
1270
    /** ++   Iterator Methods   ++ **/
1271
    /** ++++                  ++++ **/
1272
1273
    /**
1274
     * @ignore
1275
     */
1276
    public function current()
1277
    {
1278
        return current($this->items);
1279
    }
1280
1281
    /**
1282
     * @ignore
1283
     */
1284
    public function key()
1285
    {
1286
        return key($this->items);
1287
    }
1288
1289
    /**
1290
     * @ignore
1291
     */
1292
    public function next()
1293
    {
1294
        return next($this->items);
1295
    }
1296
1297
    /**
1298
     * @ignore
1299
     */
1300
    public function rewind()
1301
    {
1302
        reset($this->items);
1303
    }
1304
1305
    /**
1306
     * @ignore
1307
     */
1308
    public function valid()
1309
    {
1310
        return $this->has(key($this->items));
1311
    }
1312
1313
    /** ++++                  ++++ **/
1314
    /** ++   Countable Method   ++ **/
1315
    /** ++++                  ++++ **/
1316
1317
    /**
1318
     * Get number of items in the collection
1319
     *
1320
     * @return int
1321
     */
1322
    public function count()
1323
    {
1324
        return count($this->items);
1325
    }
1326
}