Completed
Pull Request — master (#76)
by Luke
02:39
created

Collection::diff()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 9
ccs 0
cts 0
cp 0
crap 2
rs 9.6666
c 0
b 0
f 0
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 OutOfBoundsException;
14
15
use ArrayIterator;
16
use Countable;
17
use Iterator;
18
use Traversable;
19
20
use Noz\Contracts\Arrayable;
21
use Noz\Contracts\Invokable;
22
use Noz\Contracts\CollectionInterface;
23
24
use Noz\Traits\IsArrayable;
25
use Noz\Traits\IsContainer;
26
use Noz\Traits\IsSerializable;
27
28
use function
29
    Noz\is_traversable,
30
    Noz\typeof,
31
    Noz\collect,
32
    Noz\is_arrayable,
33
    Noz\to_array,
34
    Noz\traversable_to_array;
35
36
/**
37
 * Class Collection.
38
 *
39
 * This is the abstract class that all other collection classes are based on.
40
 * Although it's possible to use a completely custom Collection class by simply
41
 * implementing the "Collectable" interface, extending this class gives you a
42
 * whole slew of convenient methods for free.
43
 *
44
 * @package Noz\Immutable
45
 *
46
 * @author Luke Visinoni <[email protected]>
47
 * @copyright Copyright (c) 2016 Luke Visinoni <[email protected]>
48
 *
49
 * @todo Implement Serializable, other Interfaces
50
 */
51
class Collection implements
52
    CollectionInterface,
53
    Arrayable,
54
    Invokable,
55
    Countable,
56
    Iterator
57
{
58
    use IsArrayable,
59
        IsContainer,
60
        IsSerializable;
61
62
    /**
63
     * @var array The collection of data this object represents
64
     */
65
    private $data = [];
66
67
    /**
68
     * @var bool True unless we have advanced past the end of the data array
69
     */
70
    protected $isValid = true;
71
72
    /**
73
     * AbstractCollection constructor.
74
     *
75
     * @param mixed $data The data to wrap
76
     */
77
    public function __construct($data = null)
78
    {
79
        if (is_null($data)) {
80
            $data = [];
81
        }
82
        if (!is_traversable($data)) {
83
            throw new InvalidArgumentException(sprintf(
84
                'Invalid input for %s. Expecting traversable data, got "%s".',
85
                __METHOD__,
86
                typeof($data)
87
            ));
88
        }
89
        $this->setData($data);
90
    }
91
92
    public function __invoke()
93
    {
94
//        $args = collect(func_get_args());
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
95
//        if ($args->hasOffset(0)) {
96
//            if ($args->hasOffset(1)) {
97
//                // two args only...
98
//                return $this->set($args->getOffset(0), $args->getOffset(1));
99
//            }
100
//            // one arg only...
101
//            $arg1 = $args->getOffset(0);
102
//            if (is_scalar($arg1)) {
103
//                return $this->get($arg1);
104
//            }
105
//            if (is_traversable($arg1)) {
106
//                return $this->union($arg1);
107
//            }
108
//            // @todo Should probably throw ane invalid arg exception here...
109
//        }
110
        return $this->toArray();
111
    }
112
113
    /**
114
     * Set underlying data array.
115
     *
116
     * Sets the collection data. This method should NEVER be called anywhere other than in __construct().
117
     *
118
     * @param array|Traversable $data The data to wrap
119
     */
120
    private function setData($data)
121
    {
122
        $this->data = traversable_to_array($data);
123
        $this->rewind();
124
    }
125
126
    /**
127
     * Get copy of underlying data array.
128
     *
129
     * Returns a copy of this collection's underlying data array. It returns a copy because collections are supposed to
130
     * be immutable. Nothing outside of the constructor should ever have direct access to the actual underlying array.
131
     *
132
     * @return array
133
     */
134
    protected function getData()
135
    {
136
        return $this->data;
137
    }
138
139
    /**
140
     * @inheritDoc
141
     */
142
    public function count()
143
    {
144
        return count($this->getData());
145
    }
146
147
    /**
148
     * Return the current element.
149
     *
150
     * Returns the current element in the collection. The internal array pointer
151
     * of the data array wrapped by the collection should not be advanced by this
152
     * method. No side effects. Return current element only.
153
     *
154
     * @return mixed
155
     */
156
    public function current()
157
    {
158
        return current($this->data);
159
    }
160
161
    /**
162
     * Return the current key.
163
     *
164
     * Returns the current key in the collection. No side effects.
165
     *
166
     * @return mixed
167
     */
168
    public function key()
169
    {
170
        return key($this->data);
171
    }
172
173
    /**
174
     * Advance the internal pointer forward.
175
     *
176
     * Although this method will return the current value after advancing the
177
     * pointer, you should not expect it to. The interface does not require it
178
     * to return any value at all.
179
     *
180
     * @return mixed
181
     */
182
    public function next()
183
    {
184
        $next = next($this->data);
185
        $key  = key($this->data);
186
        if (isset($key)) {
187
            return $next;
188
        }
189
        $this->isValid = false;
190
    }
191
192
    /**
193
     * Rewind the internal pointer.
194
     *
195
     * Return the internal pointer to the first element in the collection. Again,
196
     * this method is not required to return anything by its interface, so you
197
     * should not count on a return value.
198
     *
199
     * @return mixed
200
     */
201
    public function rewind()
202
    {
203
        $this->isValid = !empty($this->data);
204
205
        return reset($this->data);
206
    }
207
208
    /**
209
     * Is internal pointer in a valid position?
210
     *
211
     * If the internal pointer is advanced beyond the end of the collection, this method will return false.
212
     *
213
     * @return bool True if internal pointer isn't past the end
214
     */
215
    public function valid()
216
    {
217
        return $this->isValid;
218
    }
219
220
    /**
221
     * @inheritDoc
222
     */
223
    public function sort($alg = null)
224
    {
225
        if (is_null($alg)) {
226
            $alg = 'strnatcasecmp';
227
        }
228
        uasort($this->data, $alg);
229
230
        return $this;
231
    }
232
233
    /**
234
     * @inheritDoc
235
     */
236
    public function sortkeys($alg = null)
237
    {
238
        if (is_null($alg)) {
239
            $alg = 'strnatcasecmp';
240
        }
241
        uksort($this->data, $alg);
242
243
        return $this;
244
    }
245
246
    /**
247
     * Does this collection have a value at given index?
248
     *
249
     * @param mixed $index The index to check
250
     *
251
     * @return bool
252
     */
253
    public function has($index)
254
    {
255
        return array_key_exists($index, $this->getData());
256
    }
257
258
    /**
259
     * Set value at given index.
260
     *
261
     * This method simulates setting a value in this collection, but because collections are immutable, it actually
262
     * returns a copy of this collection with the value in the new collection set to specified value.
263
     *
264
     * @param mixed $index The index to set a value at
265
     * @param mixed $val   The value to set $index to
266
     *
267
     * @return $this
268
     */
269
    public function set($index, $val)
270
    {
271
        $this->data[$index] = $val;
272
273
        return $this;
274
    }
275
276
    /**
277
     * Unset (delete) value at the given index.
278
     *
279
     * Get copy of collection with given index removed.
280
     *
281
     * @param mixed $index The index to unset
282
     *
283
     * @return CollectionInterface
284
     */
285
    public function delete($index)
286
    {
287
        unset($this->data[$index]);
288
289
        return $this;
290
    }
291
292
    /**
293
     * Get index of a value.
294
     *
295
     * Given a value, this method will return the index of the first occurrence of that value.
296
     *
297
     * @param mixed $value Value to get the index of
298
     *
299
     * @return int|null|string
300
     */
301
    public function indexOf($value)
302
    {
303
        return $this->fold(function($carry, $val, $key, $iter) use ($value) {
0 ignored issues
show
Unused Code introduced by
The parameter $iter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
304
            if (is_null($carry) && $val == $value) {
305
                return $key;
306
            }
307
            return $carry;
308
        });
309
    }
310
311
    /**
312
     * Get this collection's keys as a collection.
313
     *
314
     * @return CollectionInterface Containing this collection's keys
315
     */
316
    public function keys()
317
    {
318
        return new static(array_keys($this->getData()));
319
    }
320
321
    /**
322
     * Get this collection's values as a collection.
323
     *
324
     * This method returns this collection's values but completely re-indexed (numerically).
325
     *
326
     * @return CollectionInterface Containing this collection's values
327
     */
328
    public function values()
329
    {
330
        return new static(array_values($this->getData()));
331
    }
332
333
    /**
334
     * Pad collection to a certain size.
335
     *
336
     * Returns a new collection, padded to the given size, with the given value.
337
     *
338
     * @param int   $size The number of items that should be in the collection
339
     * @param mixed $with The value to pad the collection with
340
     *
341
     * @return CollectionInterface A new collection padded to specified length
342
     */
343
    public function pad($size, $with = null)
344
    {
345
        $this->data = array_pad($this->data, $size, $with);
346
347
        return $this;
348
    }
349
350
    /**
351
     * Apply a callback to each item in collection.
352
     *
353
     * Applies a callback to each item in collection and returns a new collection
354
     * containing each iteration's return value.
355
     *
356
     * @param callable $mapper The callback to apply
357
     *
358
     * @return CollectionInterface A new collection with callback return values
359
     */
360
    public function map(callable $mapper)
361
    {
362
        $iter = 0;
363
        $transform = [];
364
        foreach ($this as $key => $val) {
365
            $transform[$key] = $mapper($val, $key, $iter++);
366
        }
367
        return new static($transform);
368
    }
369
370
    /**
371
     * Like map, except done in-place.
372
     *
373
     * @param callable $transformer A transformer callback
374
     *
375
     * @return $this
376
     */
377
    public function transform(callable $transformer)
378
    {
379
        $this->data = $this->map($transformer);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->map($transformer) of type object<Noz\Contracts\CollectionInterface> is incompatible with the declared type array of property $data.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
380
381
        return $this;
382
    }
383
384
    /**
385
     * Iterate over each item in the collection, calling $callback on it. Return false to stop iterating.
386
     *
387
     * @param callable    $callback A callback to use
388
     *
389
     * @return $this
390
     */
391
    public function each(callable $callback)
392
    {
393
        $iter = 0;
394
        foreach ($this as $key => $val) {
395
            if (!$callback($val, $key, $iter++)) {
396
                break;
397
            }
398
        }
399
400
        return $this;
401
    }
402
403
    /**
404
     * Filter the collection.
405
     *
406
     * Using a callback function, this method will filter out unwanted values, returning
407
     * a new collection containing only the values that weren't filtered.
408
     *
409
     * @param callable $callback The callback function used to filter
0 ignored issues
show
Documentation introduced by
Should the type for parameter $callback not be null|callable? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
410
     *
411
     * @return CollectionInterface A new collection with only values that weren't filtered
412
     */
413
    public function filter(callable $callback = null)
414
    {
415
        if (is_null($callback)) {
416
            $filtered = array_filter($this->data);
417
        } else {
418
            $iter     = 0;
419
            $filtered = [];
420
            foreach($this as $key => $val) {
421
                if ($callback($val, $key, $iter++)) {
422
                    $filtered[$key] = $val;
423
                }
424
            }
425
        }
426
        return new static($filtered);
427
    }
428
429
    /**
430
     * Filter the collection.
431
     *
432
     * Using a callback function, this method will filter out unwanted values, returning
433
     * a new collection containing only the values that weren't filtered.
434
     *
435
     * @param callable $callback The callback function used to filter
436
     *
437
     * @return CollectionInterface A new collection with only values that weren't filtered
438
     */
439
    public function exclude(callable $callback)
440
    {
441
        $iter = 0;
442
        $filtered = [];
443
        foreach ($this as $key => $val) {
444
            if (!$callback($val, $key, $iter++)) {
445
                $filtered[$key] = $val;
446
            }
447
        }
448
        return collect($filtered);
449
    }
450
451
    /**
452
     * Return the first item that meets given criteria.
453
     *
454
     * Using a callback function, this method will return the first item in the collection
455
     * that causes the callback function to return true.
456
     *
457
     * @param callable|null $callback The callback function
458
     * @param mixed|null    $default  The default return value
459
     *
460
     * @return mixed
461
     */
462
    public function first(callable $callback = null, $default = null)
463
    {
464
        if (is_null($callback)) {
465
            return $this->getOffset(0);
466
        }
467
468
        foreach ($this as $index => $value) {
469
            if ($callback($value, $index)) {
470
                return $value;
471
            }
472
        }
473
474
        return $default;
475
    }
476
477
    /**
478
     * Return the last item that meets given criteria.
479
     *
480
     * Using a callback function, this method will return the last item in the collection
481
     * that causes the callback function to return true.
482
     *
483
     * @param callable|null $callback The callback function
484
     * @param mixed|null    $default  The default return value
485
     *
486
     * @return mixed
487
     */
488
    public function last(callable $callback = null, $default = null)
489
    {
490
        $reverse = $this->reverse();
491
        if (is_null($callback)) {
492
            return $reverse->getOffset(0);
493
        }
494
        return $reverse->first($callback);
495
    }
496
497
    /**
498
     * Returns collection in reverse order.
499
     *
500
     * @return CollectionInterface This collection in reverse order.
501
     */
502
    public function reverse()
503
    {
504
        return collect(array_reverse($this->getData(), true));
505
    }
506
507
    /**
508
     * Get unique items.
509
     *
510
     * Returns a collection of all the unique items in this collection.
511
     *
512
     * @return CollectionInterface This collection with duplicate items removed
513
     */
514
    public function unique()
515
    {
516
        return collect(array_unique($this->getData()));
517
    }
518
519
    /**
520
     * Collection factory method.
521
     *
522
     * This method will analyze input data and determine the most appropriate Collection
523
     * class to use. It will then instantiate said Collection class with the given
524
     * data and return it.
525
     *
526
     * @param mixed $data The data to wrap
527
     *
528
     * @return CollectionInterface A collection containing $data
529
     */
530
    public static function factory($data = null)
531
    {
532
        return new Collection($data);
533
    }
534
535
    /**
536
     * Determine if structure contains all numeric values.
537
     *
538
     * @return bool
539
     */
540
    public function isNumeric()
541
    {
542
        $data = $this->getData();
543
        if (!is_traversable($data) || empty($data)) {
544
            return false;
545
        }
546
        foreach ($data as $val) {
547
            if (!is_numeric($val)) {
548
                return false;
549
            }
550
        }
551
        return true;
552
    }
553
554
    /**
555
     * @inheritdoc
556
     */
557
    public function hasOffset($offset)
558
    {
559
        try {
560
            $this->getOffsetKey($offset);
561
            return true;
562
        } catch (OutOfBoundsException $e) {
563
            return false;
564
        }
565
    }
566
567
    /**
568
     * @inheritdoc
569
     */
570
    public function getOffsetKey($offset)
571
    {
572
        if (!is_null($key = $this->fold(function($carry, $val, $key, $iter) use ($offset) {
573
            return ($iter === $offset) ? $key : $carry;
574
        }))) {
575
            return $key;
576
        }
577
        throw new OutOfBoundsException("Offset does not exist: $offset");
578
    }
579
580
    /**
581
     * @inheritdoc
582
     */
583
    public function getOffset($offset)
584
    {
585
        return $this->retrieve($this->getOffsetKey($offset));
586
    }
587
588
    /**
589
     * @param int $offset The numerical offset
590
     *
591
     * @throws OutOfBoundsException if no pair at position
592
     *
593
     * @return array
594
     */
595
    public function getOffsetPair($offset)
596
    {
597
        $pairs = $this->pairs();
598
599
        return $pairs[$this->getOffsetKey($offset)];
600
    }
601
602
    /**
603
     * Get each key/value as an array pair.
604
     *
605
     * Returns a collection of arrays where each item in the collection is [key,value]
606
     *
607
     * @return CollectionInterface
608
     */
609
    public function pairs()
610
    {
611
        return collect(array_map(
612
            function ($key, $val) {
613
                return [$key, $val];
614
            },
615
            array_keys($this->getData()),
616
            array_values($this->getData())
617
        ));
618
    }
619
620
    /**
621
     * Get duplicate values.
622
     *
623
     * Returns a collection of arrays where the key is the duplicate value
624
     * and the value is an array of keys from the original collection.
625
     *
626
     * @return CollectionInterface A new collection with duplicate values.
627
     */
628
    public function duplicates()
629
    {
630
        $dups = [];
631
        $this->walk(function ($val, $key) use (&$dups) {
0 ignored issues
show
Bug introduced by
The method walk() does not seem to exist on object<Noz\Collection>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
632
            $dups[$val][] = $key;
633
        });
634
635
        return collect($dups)->filter(function ($val) {
636
            return count($val) > 1;
637
        });
638
    }
639
640
    // END Iterator methods
641
642
    /**
643
     * Counts how many times each value occurs in a collection.
644
     *
645
     * Returns a new collection with values as keys and how many times that
646
     * value appears in the collection. Works best with scalar values but will
647
     * attempt to work on collections of objects as well.
648
     *
649
     * @return CollectionInterface
650
     *
651
     * @todo Right now, collections of arrays or objects are supported via the
652
     * __toString() or spl_object_hash()
653
     * @todo NumericCollection::counts() does the same thing...
654
     */
655
    public function frequency()
656
    {
657
        $frequency = [];
658
        foreach ($this as $key => $val) {
659
            if (!is_scalar($val)) {
660
                if (!is_object($val)) {
661
                    $val = new ArrayIterator($val);
662
                }
663
664
                if (method_exists($val, '__toString')) {
665
                    $val = (string) $val;
666
                } else {
667
                    $val = spl_object_hash($val);
668
                }
669
            }
670
            if (!isset($frequency[$val])) {
671
                $frequency[$val] = 0;
672
            }
673
            $frequency[$val]++;
674
        }
675
676
        return collect($frequency);
677
    }
678
679
    /**
680
     * @inheritDoc
681
     */
682
    public function add($index, $value)
683
    {
684
        if (!$this->has($index)) {
685
            return $this->set($index, $value);
686
        }
687
        return collect($this);
688
    }
689
690
    /**
691
     * @inheritdoc
692
     * @todo Maybe read would be a better name for this?
693
     */
694
    public function get($index, $default = null)
695
    {
696
        try {
697
            return $this->retrieve($index);
698
        } catch (OutOfBoundsException $e) {
699
            return $default;
700
        }
701
    }
702
703
    /**
704
     * @inheritdoc
705
     * @todo Maybe read would be a better name for this?
706
     */
707
    public function retrieve($index)
708
    {
709
        if (!$this->has($index)) {
710
            throw new OutOfBoundsException(__CLASS__ . ' could not retrieve value at index ' . $index);
711
        }
712
        return $this->getData()[$index];
713
    }
714
715
    /**
716
     * @inheritDoc
717
     */
718
    public function prepend($item)
719
    {
720
        $data = $this->getData();
721
        array_unshift($data, $item);
722
723
        return collect($data);
724
    }
725
726
    /**
727
     * @inheritDoc
728
     */
729
    public function append($item)
730
    {
731
        $data = $this->getData();
732
        array_push($data, $item);
733
734
        return collect($data);
735
    }
736
737
    /**
738
     * @inheritDoc
739
     */
740
    public function chunk($size)
741
    {
742
        return collect($this->fold(function($chunks, $val, $key, $iter) use ($size) {
743
            if (is_null($chunks)) {
744
                $chunks = [];
745
            }
746
            if ($iter % $size == 0) {
747
                // start new chunk
748
                array_push($chunks, []);
749
            }
750
            $chunk = array_pop($chunks);
751
            array_push($chunk, $val);
752
            array_push($chunks, $chunk);
753
754
            return $chunks;
755
        }));
756
    }
757
758
    public function combine($values)
759
    {
760
        if (!is_traversable($values)) {
761
            throw new InvalidArgumentException(sprintf(
762
                'Expecting traversable data for %s but got %s.',
763
                __METHOD__,
764
                typeof($values)
765
            ));
766
        }
767
        return collect(
768
            array_combine(
769
                $this->keys()->toArray(),
770
                collect($values)->values()->toArray()
0 ignored issues
show
Bug introduced by
It seems like $values defined by parameter $values on line 758 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
771
            )
772
        );
773
    }
774
775
    /**
776
     * @inheritDoc
777
     */
778
    public function diff($data)
779
    {
780
        return collect(
781
            array_diff(
782
                $this->toArray(),
783
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 778 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
784
            )
785
        );
786
    }
787
788
    /**
789
     * @inheritDoc
790
     */
791
    public function diffKeys($data)
792
    {
793
        return collect(
794
            array_diff_key(
795
                $this->getData(),
796
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 791 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
797
            )
798
        );
799
    }
800
801
    /**
802
     * @inheritDoc
803
     */
804
    public function nth($nth, $offset = null)
805
    {
806
        return $this->slice($offset)->filter(function($val, $key, $iter) use ($nth) {
807
            return $iter % $nth == 0;
808
        });
809
    }
810
811
    /**
812
     * @inheritDoc
813
     */
814
    public function except($indexes)
815
    {
816
        return $this->diffKeys(collect($indexes)->flip());
0 ignored issues
show
Bug introduced by
It seems like $indexes defined by parameter $indexes on line 814 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
817
    }
818
819
    /**
820
     * @inheritDoc
821
     */
822
    public function flip()
823
    {
824
        return collect(array_flip($this->getData()));
825
    }
826
827
    /**
828
     * @inheritDoc
829
     */
830
    public function intersect($data)
831
    {
832
        return collect(
833
            array_intersect(
834
                $this->toArray(),
835
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 830 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
836
            )
837
        );
838
    }
839
840
    /**
841
     * @inheritDoc
842
     */
843
    public function intersectKeys($data)
844
    {
845
        return collect(
846
            array_intersect_key(
847
                $this->toArray(),
848
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 843 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
849
            )
850
        );
851
    }
852
853
    /**
854
     * @inheritDoc
855
     */
856
    public function isEmpty(callable $callback = null)
857
    {
858
        if (!is_null($callback)) {
859
            return $this->all($callback);
860
        }
861
        return empty($this->getData());
862
    }
863
864
    /**
865
     * @inheritDoc
866
     */
867
    public function only($indices)
868
    {
869
        return $this->intersectKeys(collect($indices)->flip()->toArray());
0 ignored issues
show
Bug introduced by
It seems like $indices defined by parameter $indices on line 867 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
870
    }
871
872
    /**
873
     * @inheritDoc
874
     */
875
    public function pipe(callable $callback)
876
    {
877
        return $callback($this);
878
    }
879
880
    /**
881
     * @inheritDoc
882
     */
883
    public function random($num)
884
    {
885
        return $this->shuffle()->slice(0, $num);
886
    }
887
888
    /**
889
     * @inheritDoc
890
     */
891
    public function indicesOf($value)
892
    {
893
        return $this->filter(function($val) use ($value) {
894
            return $val == $value;
895
        })->map(function($val, $key) {
896
            return $key;
897
        });
898
    }
899
900
    /**
901
     * @inheritDoc
902
     */
903
    public function shuffle()
904
    {
905
        return collect(shuffle($this->getData()));
0 ignored issues
show
Bug introduced by
$this->getData() cannot be passed to shuffle() as the parameter $array expects a reference.
Loading history...
Documentation introduced by
shuffle($this->getData()) is of type boolean, but the function expects a array|object<Iterator>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
906
    }
907
908
    /**
909
     * @inheritDoc
910
     */
911
    public function slice($offset, $length = null)
912
    {
913
        return collect(array_slice($this->getData(), $offset, $length, true));
914
    }
915
916
    /**
917
     * @inheritDoc
918
     */
919
    public function split($num)
920
    {
921
        $count = $this->count();
922
        $size = (int)($count / $num);
923
        $mod = $count % $num;
924
        return collect($this->fold(function($chunks, $val, $key, $iter) use ($num, $size, $mod) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $iter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
925
            $chunk_count = count($chunks);
926
            if ($chunk_count <= $mod) {
927
                $size++;
928
            }
929
            if (is_null($chunks)) {
930
                // create initial chunks array...
931
                $chunks = [[]];
932
            }
933
934
            // grab the most recent chunk
935
            $chunk = array_pop($chunks);
936
937
            if (count($chunk) < $size) {
938
                // if chunk is less than the expected size, add value to it
939
                array_push($chunk, $val);
940
                array_push($chunks, $chunk);
941
            } else {
942
                // otherwise, add the chunk back and add the value to a new chunk
943
                array_push($chunks, $chunk);
944
                if ($chunk_count <= $num) {
945
                    $chunk = [$val];
946
                }
947
                array_push($chunks, $chunk);
948
            }
949
950
            return $chunks;
951
        }));
952
    }
953
954
    /**
955
     * @inheritDoc
956
     */
957
    public function union($data)
958
    {
959
        return collect(
960
            array_merge(
961
                $this->getData(),
962
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 957 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
963
            )
964
        );
965
    }
966
967
    /**
968
     * @inheritDoc
969
     */
970
    public function zip(...$data)
971
    {
972
        /** @var CollectionInterface $args The function arguments TODO: Change this to SequenceInterface when you have that interface. */
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 137 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
973
        $args = collect(func_get_args());
974
        $args->map(function($val) {
975
            if (is_arrayable($val)) {
976
                return to_array($val);
977
            } else {
978
                // @todo throw exception?
979
                return [];
980
            }
981
        });
982
        $args = $args->prepend($this->getData())
983
                     ->prepend(null);
984
985
        return collect(
986
//            array_map(
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
987
//                null,
988
//                $this->getData(),
989
//                ...$data
990
//            )
991
            call_user_func_array(
992
                'array_map',
993
                $args->toArray()
994
            )
995
        );
996
    }
997
998
    /**
999
     * @inheritDoc
1000
     */
1001
    public function fold(callable $callback, $initial = null)
1002
    {
1003
        $iter = 0;
1004
        $carry = $initial;
1005
        foreach ($this as $key => $val) {
1006
            $carry = $callback($carry, $val, $key, $iter++);
1007
        }
1008
        return $carry;
1009
    }
1010
1011
    /**
1012
     * @inheritDoc
1013
     */
1014
    public function foldl(callable $callback, $initial = null)
1015
    {
1016
        return $this->reverse()->fold($callback, $initial);
1017
    }
1018
1019
    /**
1020
     * @inheritDoc
1021
     */
1022
    public function all(callable $callback = null)
1023
    {
1024
        if (is_null($callback)) {
1025
            $callback = function($val) {
1026
                return (bool) $val;
1027
            };
1028
        }
1029
        return $this->filter($callback)->isEmpty();
1030
    }
1031
1032
    /**
1033
     * @inheritDoc
1034
     */
1035
    public function none(callable $callback = null)
1036
    {
1037
        if (is_null($callback)) {
1038
            $callback = function($val) {
1039
                return (bool) $val;
1040
            };
1041
        }
1042
        return $this->filter($callback)->isEmpty();
1043
    }
1044
1045
    // BEGIN Numeric Collection Methods
1046
    // These methods only really work on numeric data.
1047
1048
    /**
1049
     * Increment an item.
1050
     *
1051
     * Increment the item specified by $key by one value. Intended for integers
1052
     * but also works (using this term loosely) for letters. Any other data type
1053
     * it may modify is unintended behavior at best.
1054
     *
1055
     * This method modifies its internal data array rather than returning a new
1056
     * collection.
1057
     *
1058
     * @param mixed $index    The key of the item you want to increment.
1059
     * @param int   $interval The interval that $key should be incremented by
1060
     *
1061
     * @return CollectionInterface
1062
     */
1063
    public function increment($index, $interval = 1)
1064
    {
1065
        $val = $this->retrieve($index);
1066
        $val += $interval;
1067
        return $this->set($index, $val);
1068
    }
1069
1070
    /**
1071
     * Decrement an item.
1072
     *
1073
     * Frcrement the item specified by $key by one value. Intended for integers.
1074
     * Does not work for letters and if it does anything to anything else, it's
1075
     * unintended at best.
1076
     *
1077
     * This method modifies its internal data array rather than returning a new
1078
     * collection.
1079
     *
1080
     * @param mixed $index      The key of the item you want to decrement.
1081
     * @param int   $interval The interval that $key should be decremented by
1082
     *
1083
     * @return CollectionInterface
1084
     */
1085
    public function decrement($index, $interval = 1)
1086
    {
1087
        $val = $this->retrieve($index);
1088
        $val -= $interval;
1089
        return $this->set($index, $val);
1090
    }
1091
1092
    /**
1093
     * Get the sum.
1094
     *
1095
     * @return int|float The sum of all values in collection
1096
     */
1097
    public function sum()
1098
    {
1099
        return array_sum($this->toArray());
1100
    }
1101
1102
    /**
1103
     * Get the average.
1104
     *
1105
     * @return float|int The average value from the collection
1106
     */
1107
    public function average()
1108
    {
1109
        return $this->sum() / $this->count();
1110
    }
1111
1112
    /**
1113
     * Get the mode.
1114
     *
1115
     * @return float|int The mode
1116
     */
1117
    public function mode()
1118
    {
1119
        $counts = $this->counts()->toArray();
1120
        arsort($counts);
1121
        $mode = key($counts);
1122
1123
        return (strpos($mode, '.')) ? floatval($mode) : intval($mode);
1124
    }
1125
1126
    /**
1127
     * Get the median value.
1128
     *
1129
     * @return float|int The median value
1130
     */
1131
    public function median()
1132
    {
1133
        $count = $this->count();
1134
        $data  = $this->toArray();
1135
        natcasesort($data);
1136
        $middle = $count / 2;
1137
        $values = array_values($data);
1138
        if ($count % 2 == 0) {
1139
            // even number, use middle
1140
            $low  = $values[$middle - 1];
1141
            $high = $values[$middle];
1142
1143
            return ($low + $high) / 2;
1144
        }
1145
        // odd number return median
1146
        return $values[$middle];
1147
    }
1148
1149
    /**
1150
     * Get the maximum value.
1151
     *
1152
     * @return mixed The maximum
1153
     */
1154
    public function max()
1155
    {
1156
        return max($this->getData());
1157
    }
1158
1159
    /**
1160
     * Get the minimum value.
1161
     *
1162
     * @return mixed The minimum
1163
     */
1164
    public function min()
1165
    {
1166
        return min($this->getData());
1167
    }
1168
1169
    /**
1170
     * Get the number of times each item occurs in the collection.
1171
1172
     * This method will return a NumericCollection where keys are the
1173
     * values and values are the number of times that value occurs in
1174
     * the original collection.
1175
1176
     * @return CollectionInterface
1177
     */
1178
    public function counts()
1179
    {
1180
        return collect(array_count_values($this->toArray()));
1181
    }
1182
1183
    /**
1184
     * @param $serialized
1185
     */
1186
    public function unserialize($serialized)
1187
    {
1188
        $this->setData(unserialize($serialized));
1189
    }
1190
1191
}
1192