Completed
Pull Request — master (#49)
by Luke
06:27 queued 04:14
created

Collection::duplicates()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
100
            }
101
            // @todo Should probably throw ane invalid arg exception here...
102
        }
103
        return $this->toArray();
104
    }
105
106
    /**
107
     * Set underlying data array.
108
     *
109
     * Sets the collection data. This method should NEVER be called anywhere other than in __construct().
110
     *
111
     * @param array|Traversable $data The data to wrap
112
     */
113
    private function setData($data)
114
    {
115
        $arr = [];
116
        foreach ($data as $index => $value) {
117
            $arr[$index] = $value;
118
        }
119
        $this->data = $arr;
120
        $this->rewind();
121
    }
122
123
    /**
124
     * Get copy of underlying data array.
125
     *
126
     * Returns a copy of this collection's underlying data array. It returns a copy because collections are supposed to
127
     * be immutable. Nothing outside of the constructor should ever have direct access to the actual underlying array.
128
     *
129
     * @return array
130
     */
131
    protected function getData()
132
    {
133
        return $data = $this->data;
0 ignored issues
show
Unused Code introduced by
$data is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
134
    }
135
136
    /**
137
     * @inheritDoc
138
     */
139
    public function count(callable $callback = null)
140
    {
141
        if (!is_null($callback)) {
142
            return $this->filter($callback)->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
        $data = $this->getData();
229
        uasort($data, $alg);
230
231
        return collect($data);
232
    }
233
234
    /**
235
     * @inheritDoc
236
     */
237
    public function sortkeys($alg = null)
238
    {
239
        if (is_null($alg)) {
240
            $alg = 'strnatcasecmp';
241
        }
242
        $data = $this->getData();
243
        uksort($data, $alg);
244
245
        return collect($data);
246
    }
247
248
    /**
249
     * Does this collection have a value at given index?
250
     *
251
     * @param mixed $index The index to check
252
     *
253
     * @return bool
254
     */
255
    public function has($index)
256
    {
257
        return array_key_exists($index, $this->getData());
258
    }
259
260
    /**
261
     * Set value at given index.
262
     *
263
     * This method simulates setting a value in this collection, but because collections are immutable, it actually
264
     * returns a copy of this collection with the value in the new collection set to specified value.
265
     *
266
     * @param mixed $index The index to set a value at
267
     * @param mixed $val   The value to set $index to
268
     *
269
     * @return CollectionInterface
270
     */
271
    public function set($index, $val)
272
    {
273
        $copy = $this->getData();
274
        $copy[$index] = $val;
275
        return collect($copy);
276
    }
277
278
    /**
279
     * Unset (delete) value at the given index.
280
     *
281
     * Get copy of collection with given index removed.
282
     *
283
     * @param mixed $index The index to unset
284
     *
285
     * @return CollectionInterface
286
     */
287
    public function delete($index)
288
    {
289
        return $this->except([$index]);
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->foldRight(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 collect(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 collect(array_values($this->getData()));
331
    }
332
333
    /**
334
     * Determine if this collection contains a value.
335
     *
336
     * Allows you to pass in a value or a callback function and optionally an index,
337
     * and tells you whether or not this collection contains that value.
338
     * If the $index param is specified, only that index will be looked under.
339
     *
340
     * @param mixed|callable $value The value to check for
341
     * @param mixed          $index The (optional) index to look under
342
     *
343
     * @return bool True if this collection contains $value
344
     *
345
     * @todo Maybe add $identical param for identical comparison (===)
346
     * @todo Allow negative offset for second param
347
     */
348
    public function contains($value, $index = null)
349
    {
350
        return (bool) $this->first(function ($val, $key) use ($value, $index) {
351
            if (is_callable($value)) {
352
                $found = $value($val, $key);
353
            } else {
354
                $found = ($value == $val);
355
            }
356
            if ($found) {
357
                if (is_null($index)) {
358
                    return true;
359
                }
360
                if (is_array($index)) {
361
                    return in_array($key, $index);
362
                }
363
364
                return $key == $index;
365
            }
366
367
            return false;
368
        });
369
    }
370
371
    /**
372
     * Pad collection to a certain size.
373
     *
374
     * Returns a new collection, padded to the given size, with the given value.
375
     *
376
     * @param int   $size The number of items that should be in the collection
377
     * @param mixed $with The value to pad the collection with
378
     *
379
     * @return CollectionInterface A new collection padded to specified length
380
     */
381
    public function pad($size, $with = null)
382
    {
383
        return collect(array_pad($this->getData(), $size, $with));
384
    }
385
386
    /**
387
     * Apply a callback to each item in collection.
388
     *
389
     * Applies a callback to each item in collection and returns a new collection
390
     * containing each iteration's return value.
391
     *
392
     * @param callable $callback The callback to apply
393
     *
394
     * @return CollectionInterface A new collection with callback return values
395
     */
396
    public function map(callable $callback)
397
    {
398
        $iter = 0;
399
        $transform = [];
400
        foreach ($this as $key => $val) {
401
            $transform[$key] = $callback($val, $key, $iter++);
402
        }
403
        return collect($transform);
404
    }
405
406
    /**
407
     * Iterate over each item in the collection, calling $callback on it. Return false to stop iterating.
408
     *
409
     * @param callable    $callback A callback to use
410
     *
411
     * @return $this
412
     */
413
    public function each(callable $callback)
414
    {
415
        foreach ($this as $key => $val) {
416
            if (!$callback($val, $key)) {
417
                break;
418
            }
419
        }
420
421
        return $this;
422
    }
423
424
    /**
425
     * Filter the collection.
426
     *
427
     * Using a callback function, this method will filter out unwanted values, returning
428
     * a new collection containing only the values that weren't filtered.
429
     *
430
     * @param callable $callback The callback function used to filter
431
     *
432
     * @return CollectionInterface A new collection with only values that weren't filtered
433
     */
434
    public function filter(callable $callback)
435
    {
436
        $iter = 0;
437
        $filtered = [];
438
        foreach ($this as $key => $val) {
439
            if ($callback($val, $key, $iter++)) {
440
                $filtered[$key] = $val;
441
            }
442
        }
443
        return collect($filtered);
444
    }
445
446
    /**
447
     * Filter the collection.
448
     *
449
     * Using a callback function, this method will filter out unwanted values, returning
450
     * a new collection containing only the values that weren't filtered.
451
     *
452
     * @param callable $callback The callback function used to filter
453
     *
454
     * @return CollectionInterface A new collection with only values that weren't filtered
455
     */
456
    public function exclude(callable $callback)
457
    {
458
        $iter = 0;
459
        $filtered = [];
460
        foreach ($this as $key => $val) {
461
            if (!$callback($val, $key, $iter++)) {
462
                $filtered[$key] = $val;
463
            }
464
        }
465
        return collect($filtered);
466
    }
467
468
    /**
469
     * Return the first item that meets given criteria.
470
     *
471
     * Using a callback function, this method will return the first item in the collection
472
     * that causes the callback function to return true.
473
     *
474
     * @param callable|null $callback The callback function
475
     * @param mixed|null    $default  The default return value
476
     *
477
     * @return mixed
478
     */
479
    public function first(callable $callback = null, $default = null)
480
    {
481
        if (is_null($callback)) {
482
            return $this->getOffset(0);
483
        }
484
485
        foreach ($this as $index => $value) {
486
            if ($callback($value, $index)) {
487
                return $value;
488
            }
489
        }
490
491
        return $default;
492
    }
493
494
    /**
495
     * Return the last item that meets given criteria.
496
     *
497
     * Using a callback function, this method will return the last item in the collection
498
     * that causes the callback function to return true.
499
     *
500
     * @param callable|null $callback The callback function
501
     * @param mixed|null    $default  The default return value
502
     *
503
     * @return mixed
504
     */
505
    public function last(callable $callback = null, $default = null)
506
    {
507
        $reverse = $this->reverse();
508
        if (is_null($callback)) {
509
            return $reverse->getOffset(0);
510
        }
511
        return $reverse->first($callback);
512
    }
513
514
    /**
515
     * Returns collection in reverse order.
516
     *
517
     * @return CollectionInterface This collection in reverse order.
518
     */
519
    public function reverse()
520
    {
521
        return collect(array_reverse($this->getData(), true));
522
    }
523
524
    /**
525
     * Get unique items.
526
     *
527
     * Returns a collection of all the unique items in this collection.
528
     *
529
     * @return CollectionInterface This collection with duplicate items removed
530
     */
531
    public function unique()
532
    {
533
        return collect(array_unique($this->getData()));
534
    }
535
536
    /**
537
     * Collection factory method.
538
     *
539
     * This method will analyze input data and determine the most appropriate Collection
540
     * class to use. It will then instantiate said Collection class with the given
541
     * data and return it.
542
     *
543
     * @param mixed $data The data to wrap
544
     *
545
     * @return CollectionInterface A collection containing $data
546
     */
547
    public static function factory($data = null)
548
    {
549
        return new Collection($data);
550
    }
551
552
    /**
553
     * Determine if structure contains all numeric values.
554
     *
555
     * @return bool
556
     */
557
    public function isNumeric()
558
    {
559
        $data = $this->getData();
560
        if (!is_traversable($data) || empty($data)) {
561
            return false;
562
        }
563
        foreach ($data as $val) {
564
            if (!is_numeric($val)) {
565
                return false;
566
            }
567
        }
568
        return true;
569
    }
570
571
    /**
572
     * @inheritdoc
573
     */
574
    public function hasOffset($offset)
575
    {
576
        try {
577
            $this->getOffsetKey($offset);
578
            return true;
579
        } catch (OutOfBoundsException $e) {
580
            return false;
581
        }
582
    }
583
584
    /**
585
     * @inheritdoc
586
     */
587
    public function getOffsetKey($offset)
588
    {
589
        if (!is_null($key = $this->foldRight(function($carry, $val, $key, $iter) use ($offset) {
590
            return ($iter === $offset) ? $key : $carry;
591
        }))) {
592
            return $key;
593
        }
594
        throw new OutOfBoundsException("Offset does not exist: $offset");
595
    }
596
597
    /**
598
     * @inheritdoc
599
     */
600
    public function getOffset($offset)
601
    {
602
        return $this->retrieve($this->getOffsetKey($offset));
603
    }
604
605
    /**
606
     * @param int $offset The numerical offset
607
     *
608
     * @throws OutOfBoundsException if no pair at position
609
     *
610
     * @return array
611
     */
612
    public function getOffsetPair($offset)
613
    {
614
        $pairs = $this->pairs();
615
616
        return $pairs[$this->getOffsetKey($offset)];
617
    }
618
619
    /**
620
     * Get each key/value as an array pair.
621
     *
622
     * Returns a collection of arrays where each item in the collection is [key,value]
623
     *
624
     * @return CollectionInterface
625
     */
626
    public function pairs()
627
    {
628
        return collect(array_map(
629
            function ($key, $val) {
630
                return [$key, $val];
631
            },
632
            array_keys($this->getData()),
633
            array_values($this->getData())
634
        ));
635
    }
636
637
    /**
638
     * Get duplicate values.
639
     *
640
     * Returns a collection of arrays where the key is the duplicate value
641
     * and the value is an array of keys from the original collection.
642
     *
643
     * @return CollectionInterface A new collection with duplicate values.
644
     */
645
    public function duplicates()
646
    {
647
        $dups = [];
648
        $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\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...
649
            $dups[$val][] = $key;
650
        });
651
652
        return collect($dups)->filter(function ($val) {
653
            return count($val) > 1;
654
        });
655
    }
656
657
    // END Iterator methods
658
659
    /**
660
     * Counts how many times each value occurs in a collection.
661
     *
662
     * Returns a new collection with values as keys and how many times that
663
     * value appears in the collection. Works best with scalar values but will
664
     * attempt to work on collections of objects as well.
665
     *
666
     * @return CollectionInterface
667
     *
668
     * @todo Right now, collections of arrays or objects are supported via the
669
     * __toString() or spl_object_hash()
670
     * @todo NumericCollection::counts() does the same thing...
671
     */
672
    public function frequency()
673
    {
674
        $frequency = [];
675
        foreach ($this as $key => $val) {
676
            if (!is_scalar($val)) {
677
                if (!is_object($val)) {
678
                    $val = new ArrayIterator($val);
679
                }
680
681
                if (method_exists($val, '__toString')) {
682
                    $val = (string) $val;
683
                } else {
684
                    $val = spl_object_hash($val);
685
                }
686
            }
687
            if (!isset($frequency[$val])) {
688
                $frequency[$val] = 0;
689
            }
690
            $frequency[$val]++;
691
        }
692
693
        return collect($frequency);
694
    }
695
696
    /**
697
     * @inheritDoc
698
     */
699
    public function add($index, $value)
700
    {
701
        if (!$this->has($index)) {
702
            return $this->set($index, $value);
703
        }
704
        return collect($this);
705
    }
706
707
    /**
708
     * @inheritdoc
709
     * @todo Maybe read would be a better name for this?
710
     */
711
    public function get($index, $default = null)
712
    {
713
        try {
714
            return $this->retrieve($index);
715
        } catch (OutOfBoundsException $e) {
716
            return $default;
717
        }
718
    }
719
720
    /**
721
     * @inheritdoc
722
     * @todo Maybe read would be a better name for this?
723
     */
724
    public function retrieve($index)
725
    {
726
        if (!$this->has($index)) {
727
            throw new OutOfBoundsException(__CLASS__ . ' could not retrieve value at index ' . $index);
728
        }
729
        return $this->getData()[$index];
730
    }
731
732
    /**
733
     * @inheritDoc
734
     */
735
    public function prepend($item)
736
    {
737
        $data = $this->getData();
738
        array_unshift($data, $item);
739
740
        return collect($data);
741
    }
742
743
    /**
744
     * @inheritDoc
745
     */
746
    public function append($item)
747
    {
748
        $data = $this->getData();
749
        array_push($data, $item);
750
751
        return collect($data);
752
    }
753
754
    /**
755
     * @inheritDoc
756
     */
757
    public function chunk($size)
758
    {
759
        return collect($this->foldRight(function($chunks, $val, $key, $iter) use ($size) {
760
            if (is_null($chunks)) {
761
                $chunks = [];
762
            }
763
            if ($iter % $size == 0) {
764
                // start new chunk
765
                array_push($chunks, []);
766
            }
767
            $chunk = array_pop($chunks);
768
            array_push($chunk, $val);
769
            array_push($chunks, $chunk);
770
771
            return $chunks;
772
        }));
773
    }
774
775
    public function combine($values)
776
    {
777
        if (!is_traversable($values)) {
778
            throw new InvalidArgumentException(sprintf(
779
                'Expecting traversable data for %s but got %s.',
780
                __METHOD__,
781
                typeof($values)
782
            ));
783
        }
784
        return collect(
785
            array_combine(
786
                $this->keys()->toArray(),
787
                collect($values)->values()->toArray()
0 ignored issues
show
Bug introduced by
It seems like $values defined by parameter $values on line 775 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...
788
            )
789
        );
790
    }
791
792
    /**
793
     * @inheritDoc
794
     */
795
    public function diff($data)
796
    {
797
        return collect(
798
            array_diff(
799
                $this->toArray(),
800
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 795 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...
801
            )
802
        );
803
    }
804
805
    /**
806
     * @inheritDoc
807
     */
808
    public function diffKeys($data)
809
    {
810
        return collect(
811
            array_diff_key(
812
                $this->getData(),
813
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 808 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...
814
            )
815
        );
816
    }
817
818
    /**
819
     * @inheritDoc
820
     */
821
    public function nth($nth, $offset = null)
822
    {
823
        return $this->slice($offset)->filter(function($val, $key, $iter) use ($nth) {
824
            return $iter % $nth == 0;
825
        });
826
    }
827
828
    /**
829
     * @inheritDoc
830
     */
831
    public function except($indexes)
832
    {
833
        return $this->diffKeys(collect($indexes)->flip());
0 ignored issues
show
Bug introduced by
It seems like $indexes defined by parameter $indexes on line 831 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...
834
    }
835
836
    /**
837
     * @inheritDoc
838
     */
839
    public function flip()
840
    {
841
        return collect(array_flip($this->getData()));
842
    }
843
844
    /**
845
     * @inheritDoc
846
     */
847
    public function intersect($data)
848
    {
849
        return collect(
850
            array_intersect(
851
                $this->toArray(),
852
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 847 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...
853
            )
854
        );
855
    }
856
857
    /**
858
     * @inheritDoc
859
     */
860
    public function intersectKeys($data)
861
    {
862
        return collect(
863
            array_intersect_key(
864
                $this->toArray(),
865
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 860 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...
866
            )
867
        );
868
    }
869
870
    /**
871
     * @inheritDoc
872
     */
873
    public function isEmpty(callable $callback = null)
874
    {
875
        if (!is_null($callback)) {
876
            return $this->all($callback);
877
        }
878
        return empty($this->getData());
879
    }
880
881
    /**
882
     * @inheritDoc
883
     */
884
    public function only($indices)
885
    {
886
        return $this->intersectKeys(collect($indices)->flip()->toArray());
0 ignored issues
show
Bug introduced by
It seems like $indices defined by parameter $indices on line 884 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...
887
    }
888
889
    /**
890
     * @inheritDoc
891
     */
892
    public function pipe(callable $callback)
893
    {
894
        return $callback($this);
895
    }
896
897
    /**
898
     * @inheritDoc
899
     */
900
    public function random($num)
901
    {
902
        return $this->shuffle()->slice(0, $num);
903
    }
904
905
    /**
906
     * @inheritDoc
907
     */
908
    public function indicesOf($value)
909
    {
910
        return $this->filter(function($val) use ($value) {
911
            return $val == $value;
912
        })->map(function($val, $key) {
913
            return $key;
914
        });
915
    }
916
917
    /**
918
     * @inheritDoc
919
     */
920
    public function shuffle()
921
    {
922
        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...
923
    }
924
925
    /**
926
     * @inheritDoc
927
     */
928
    public function slice($offset, $length = null)
929
    {
930
        return collect(array_slice($this->getData(), $offset, $length, true));
931
    }
932
933
    /**
934
     * @inheritDoc
935
     */
936
    public function split($num)
937
    {
938
        $count = $this->count();
939
        $size = (int)($count / $num);
940
        $mod = $count % $num;
941
        return collect($this->foldRight(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...
942
            $chunk_count = count($chunks);
943
            if ($chunk_count <= $mod) {
944
                $size++;
945
            }
946
            if (is_null($chunks)) {
947
                // create initial chunks array...
948
                $chunks = [[]];
949
            }
950
951
            // grab the most recent chunk
952
            $chunk = array_pop($chunks);
953
954
            if (count($chunk) < $size) {
955
                // if chunk is less than the expected size, add value to it
956
                array_push($chunk, $val);
957
                array_push($chunks, $chunk);
958
            } else {
959
                // otherwise, add the chunk back and add the value to a new chunk
960
                array_push($chunks, $chunk);
961
                if ($chunk_count <= $num) {
962
                    $chunk = [$val];
963
                }
964
                array_push($chunks, $chunk);
965
            }
966
967
            return $chunks;
968
        }));
969
    }
970
971
    /**
972
     * @inheritDoc
973
     */
974
    public function union($data)
975
    {
976
        return collect(
977
            array_merge(
978
                $this->getData(),
979
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 974 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...
980
            )
981
        );
982
    }
983
984
    /**
985
     * @inheritDoc
986
     */
987
    public function zip(...$data)
988
    {
989
        /** @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...
990
        $args = collect(func_get_args());
991
        $args->map(function($val) {
992
            if (is_arrayable($val)) {
993
                return to_array($val);
994
            } else {
995
                // @todo throw exception?
996
                return [];
997
            }
998
        });
999
        $args = $args->prepend($this->getData())
1000
                     ->prepend(null);
1001
1002
        return collect(
1003
//            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...
1004
//                null,
1005
//                $this->getData(),
1006
//                ...$data
1007
//            )
1008
            call_user_func_array(
1009
                'array_map',
1010
                $args->toArray()
1011
            )
1012
        );
1013
    }
1014
1015
    /**
1016
     * @inheritDoc
1017
     */
1018
    public function foldRight(callable $callback, $initial = null)
1019
    {
1020
        $iter = 0;
1021
        $carry = $initial;
1022
        foreach ($this as $key => $val) {
1023
            $carry = $callback($carry, $val, $key, $iter++);
1024
        }
1025
        return $carry;
1026
    }
1027
1028
    /**
1029
     * @inheritDoc
1030
     */
1031
    public function foldLeft(callable $callback, $initial = null)
1032
    {
1033
        return $this->reverse()->foldRight($callback, $initial);
1034
    }
1035
1036
    /**
1037
     * @inheritDoc
1038
     */
1039
    public function all(callable $callback = null)
1040
    {
1041
        if (is_null($callback)) {
1042
            $callback = function($val) {
1043
                return (bool) $val;
1044
            };
1045
        }
1046
        return $this->filter($callback)->isEmpty();
1047
    }
1048
1049
    /**
1050
     * @inheritDoc
1051
     */
1052
    public function none(callable $callback = null)
1053
    {
1054
        if (is_null($callback)) {
1055
            $callback = function($val) {
1056
                return (bool) $val;
1057
            };
1058
        }
1059
        return $this->filter($callback)->isEmpty();
1060
    }
1061
1062
    // BEGIN Numeric Collection Methods
1063
    // These methods only really work on numeric data.
1064
1065
    /**
1066
     * Increment an item.
1067
     *
1068
     * Increment the item specified by $key by one value. Intended for integers
1069
     * but also works (using this term loosely) for letters. Any other data type
1070
     * it may modify is unintended behavior at best.
1071
     *
1072
     * This method modifies its internal data array rather than returning a new
1073
     * collection.
1074
     *
1075
     * @param mixed $index    The key of the item you want to increment.
1076
     * @param int   $interval The interval that $key should be incremented by
1077
     *
1078
     * @return CollectionInterface
1079
     */
1080
    public function increment($index, $interval = 1)
1081
    {
1082
        $val = $this->retrieve($index);
1083
        $val += $interval;
1084
        return $this->set($index, $val);
1085
    }
1086
1087
    /**
1088
     * Decrement an item.
1089
     *
1090
     * Frcrement the item specified by $key by one value. Intended for integers.
1091
     * Does not work for letters and if it does anything to anything else, it's
1092
     * unintended at best.
1093
     *
1094
     * This method modifies its internal data array rather than returning a new
1095
     * collection.
1096
     *
1097
     * @param mixed $index      The key of the item you want to decrement.
1098
     * @param int   $interval The interval that $key should be decremented by
1099
     *
1100
     * @return CollectionInterface
1101
     */
1102
    public function decrement($index, $interval = 1)
1103
    {
1104
        $val = $this->retrieve($index);
1105
        $val -= $interval;
1106
        return $this->set($index, $val);
1107
    }
1108
1109
    /**
1110
     * Get the sum.
1111
     *
1112
     * @return int|float The sum of all values in collection
1113
     */
1114
    public function sum()
1115
    {
1116
        return array_sum($this->toArray());
1117
    }
1118
1119
    /**
1120
     * Get the average.
1121
     *
1122
     * @return float|int The average value from the collection
1123
     */
1124
    public function average()
1125
    {
1126
        return $this->sum() / $this->count();
1127
    }
1128
1129
    /**
1130
     * Get the mode.
1131
     *
1132
     * @return float|int The mode
1133
     */
1134
    public function mode()
1135
    {
1136
        $counts = $this->counts()->toArray();
1137
        arsort($counts);
1138
        $mode = key($counts);
1139
1140
        return (strpos($mode, '.')) ? floatval($mode) : intval($mode);
1141
    }
1142
1143
    /**
1144
     * Get the median value.
1145
     *
1146
     * @return float|int The median value
1147
     */
1148
    public function median()
1149
    {
1150
        $count = $this->count();
1151
        $data  = $this->toArray();
1152
        natcasesort($data);
1153
        $middle = $count / 2;
1154
        $values = array_values($data);
1155
        if ($count % 2 == 0) {
1156
            // even number, use middle
1157
            $low  = $values[$middle - 1];
1158
            $high = $values[$middle];
1159
1160
            return ($low + $high) / 2;
1161
        }
1162
        // odd number return median
1163
        return $values[$middle];
1164
    }
1165
1166
    /**
1167
     * Get the maximum value.
1168
     *
1169
     * @return mixed The maximum
1170
     */
1171
    public function max()
1172
    {
1173
        return max($this->getData());
1174
    }
1175
1176
    /**
1177
     * Get the minimum value.
1178
     *
1179
     * @return mixed The minimum
1180
     */
1181
    public function min()
1182
    {
1183
        return min($this->getData());
1184
    }
1185
1186
    /**
1187
     * Get the number of times each item occurs in the collection.
1188
1189
     * This method will return a NumericCollection where keys are the
1190
     * values and values are the number of times that value occurs in
1191
     * the original collection.
1192
1193
     * @return CollectionInterface
1194
     */
1195
    public function counts()
1196
    {
1197
        return collect(array_count_values($this->toArray()));
1198
    }
1199
}
1200