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

Collection::split()   B

Complexity

Conditions 5
Paths 1

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 20
nc 1
nop 1
dl 0
loc 34
ccs 0
cts 0
cp 0
crap 30
rs 8.439
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 Noz\Immutable\Sequence;
14
use OutOfBoundsException;
15
16
use ArrayIterator;
17
use Countable;
18
use Iterator;
19
use ReflectionFunction;
20
use Traversable;
21
22
use Noz\Contracts\Arrayable;
23
use Noz\Contracts\Invokable;
24
use Noz\Contracts\CollectionInterface;
25
26
use Noz\Traits\IsArrayable;
27
use Noz\Traits\IsContainer;
28
use Noz\Traits\IsSerializable;
29
30
use function
31
    Noz\is_traversable,
32
    Noz\typeof,
33
    Noz\collect,
34
    Noz\is_arrayable,
35
    Noz\to_array,
36
    Noz\traversable_to_array,
37
    Noz\normalize_offset;
38
39
/**
40
 * Class Collection.
41
 *
42
 * This is the abstract class that all other collection classes are based on.
43
 * Although it's possible to use a completely custom Collection class by simply
44
 * implementing the "Collectable" interface, extending this class gives you a
45
 * whole slew of convenient methods for free.
46
 *
47
 * @package Noz\Immutable
48
 *
49
 * @author Luke Visinoni <[email protected]>
50
 * @copyright Copyright (c) 2016 Luke Visinoni <[email protected]>
51
 *
52
 * @todo Implement Serializable, other Interfaces
53
 */
54
class Collection implements
55
    CollectionInterface,
56
    Arrayable,
57
    Invokable,
58
    Countable,
59
    Iterator
60
{
61
    use IsArrayable,
62
        IsContainer,
63
        IsSerializable;
64
65
    /**
66
     * @var array The collection of data this object represents
67
     */
68
    private $data = [];
69
70
    /**
71
     * @var bool True unless we have advanced past the end of the data array
72
     */
73
    protected $isValid = true;
74
75
    /**
76
     * AbstractCollection constructor.
77
     *
78
     * @param mixed $data The data to wrap
79
     */
80
    public function __construct($data = null)
81
    {
82
        if (is_null($data)) {
83
            $data = [];
84
        }
85
        if (!is_traversable($data)) {
86
            throw new InvalidArgumentException(sprintf(
87
                'Invalid input for %s. Expecting traversable data, got "%s".',
88
                __METHOD__,
89
                typeof($data)
90
            ));
91
        }
92
        $this->setData($data);
93
    }
94
95
    public function __invoke()
96
    {
97
//        $args = collect(func_get_args());
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...
98
//        if ($args->hasOffset(0)) {
99
//            if ($args->hasOffset(1)) {
100
//                // two args only...
101
//                return $this->set($args->getOffset(0), $args->getOffset(1));
102
//            }
103
//            // one arg only...
104
//            $arg1 = $args->getOffset(0);
105
//            if (is_scalar($arg1)) {
106
//                return $this->get($arg1);
107
//            }
108
//            if (is_traversable($arg1)) {
109
//                return $this->union($arg1);
110
//            }
111
//            // @todo Should probably throw ane invalid arg exception here...
112
//        }
113
        return $this->toArray();
114
    }
115
116
    /**
117
     * Set underlying data array.
118
     *
119
     * Sets the collection data. This method should NEVER be called anywhere other than in __construct().
120
     *
121
     * @param array|Traversable $data The data to wrap
122
     */
123
    private function setData($data)
124
    {
125
        $this->data = traversable_to_array($data);
126
        $this->rewind();
127
    }
128
129
    /**
130
     * Get copy of underlying data array.
131
     *
132
     * Returns a copy of this collection's underlying data array. It returns a copy because collections are supposed to
133
     * be immutable. Nothing outside of the constructor should ever have direct access to the actual underlying array.
134
     *
135
     * @return array
136
     */
137
    protected function getData()
138
    {
139
        return $this->data;
140
    }
141
142
    /**
143
     * @inheritDoc
144
     */
145
    public function count()
146
    {
147
        return count($this->getData());
148
    }
149
150
    /**
151
     * Return the current element.
152
     *
153
     * Returns the current element in the collection. The internal array pointer
154
     * of the data array wrapped by the collection should not be advanced by this
155
     * method. No side effects. Return current element only.
156
     *
157
     * @return mixed
158
     */
159
    public function current()
160
    {
161
        return current($this->data);
162
    }
163
164
    /**
165
     * Return the current key.
166
     *
167
     * Returns the current key in the collection. No side effects.
168
     *
169
     * @return mixed
170
     */
171
    public function key()
172
    {
173
        return key($this->data);
174
    }
175
176
    /**
177
     * Advance the internal pointer forward.
178
     *
179
     * Although this method will return the current value after advancing the
180
     * pointer, you should not expect it to. The interface does not require it
181
     * to return any value at all.
182
     *
183
     * @return mixed
184
     */
185
    public function next()
186
    {
187
        $next = next($this->data);
188
        $key  = key($this->data);
189
        if (isset($key)) {
190
            return $next;
191
        }
192
        $this->isValid = false;
193
    }
194
195
    /**
196
     * Rewind the internal pointer.
197
     *
198
     * Return the internal pointer to the first element in the collection. Again,
199
     * this method is not required to return anything by its interface, so you
200
     * should not count on a return value.
201
     *
202
     * @return mixed
203
     */
204
    public function rewind()
205
    {
206
        $this->isValid = !empty($this->data);
207
208
        return reset($this->data);
209
    }
210
211
    /**
212
     * Is internal pointer in a valid position?
213
     *
214
     * If the internal pointer is advanced beyond the end of the collection, this method will return false.
215
     *
216
     * @return bool True if internal pointer isn't past the end
217
     */
218
    public function valid()
219
    {
220
        return $this->isValid;
221
    }
222
223
    /**
224
     * @inheritDoc
225
     */
226
    public function sort($alg = null)
227
    {
228
        if (is_null($alg)) {
229
            $alg = 'strnatcasecmp';
230
        }
231
        uasort($this->data, $alg);
232
233
        return $this;
234
    }
235
236
    /**
237
     * @inheritDoc
238
     */
239
    public function sortkeys($alg = null)
240
    {
241
        if (is_null($alg)) {
242
            $alg = 'strnatcasecmp';
243
        }
244
        uksort($this->data, $alg);
245
246
        return $this;
247
    }
248
249
    /**
250
     * Does this collection have a value at given index?
251
     *
252
     * @param mixed $index The index to check
253
     *
254
     * @return bool
255
     */
256
    public function has($index)
257
    {
258
        return array_key_exists($index, $this->getData());
259
    }
260
261
    /**
262
     * Set value at given index.
263
     *
264
     * This method simulates setting a value in this collection, but because collections are immutable, it actually
265
     * returns a copy of this collection with the value in the new collection set to specified value.
266
     *
267
     * @param mixed $index The index to set a value at
268
     * @param mixed $val   The value to set $index to
269
     *
270
     * @return $this
271
     */
272
    public function set($index, $val)
273
    {
274
        $this->data[$index] = $val;
275
276
        return $this;
277
    }
278
279
    /**
280
     * Unset (delete) value at the given index.
281
     *
282
     * Get copy of collection with given index removed.
283
     *
284
     * @param mixed $index The index to unset
285
     *
286
     * @return Collection
287
     */
288
    public function delete($index)
289
    {
290
        unset($this->data[$index]);
291
292
        return $this;
293
    }
294
295
    /**
296
     * Get index of a value.
297
     *
298
     * Given a value, this method will return the index of the first occurrence of that value.
299
     *
300
     * @param mixed $value Value to get the index of
301
     *
302
     * @return int|null|string
303
     */
304
    public function indexOf($value)
305
    {
306
        return $this->fold(function($carry, $val, $key) use ($value) {
307
            if (is_null($carry) && $val == $value) {
308
                return $key;
309
            }
310
            return $carry;
311
        });
312
    }
313
314
    /**
315
     * Get this collection's keys as a collection.
316
     *
317
     * @return Collection Containing this collection's keys
318
     */
319
    public function keys()
320
    {
321
        return static::factory(array_keys($this->getData()));
322
    }
323
324
    /**
325
     * Get this collection's values as a collection.
326
     *
327
     * This method returns this collection's values but completely re-indexed (numerically).
328
     *
329
     * @return Collection Containing this collection's values
330
     */
331
    public function values()
332
    {
333
        return static::factory(array_values($this->getData()));
334
    }
335
336
    /**
337
     * Pad collection to a certain size.
338
     *
339
     * Returns a new collection, padded to the given size, with the given value.
340
     *
341
     * @param int   $size The number of items that should be in the collection
342
     * @param mixed $with The value to pad the collection with
343
     *
344
     * @return Collection A new collection padded to specified length
345
     */
346
    public function pad($size, $with = null)
347
    {
348
        $this->data = array_pad($this->data, $size, $with);
349
350
        return $this;
351
    }
352
353
    /**
354
     * Apply a callback to each item in collection.
355
     *
356
     * Applies a callback to each item in collection and returns a new collection
357
     * containing each iteration's return value.
358
     *
359
     * @param callable $mapper The callback to apply
360
     *
361
     * @return Collection A new collection with callback return values
362
     */
363
    public function map(callable $mapper)
364
    {
365
        $iter = 0;
366
        $transform = [];
367
        foreach ($this as $key => $val) {
368
            $transform[$key] = $mapper($val, $key, $iter++);
369
        }
370
        return static::factory($transform);
371
    }
372
373
    /**
374
     * Like map, except done in-place.
375
     *
376
     * @param callable $transformer A transformer callback
377
     *
378
     * @return $this
379
     */
380
    public function transform(callable $transformer)
381
    {
382
        $this->data = $this->map($transformer);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->map($transformer) of type object<Noz\Collection> 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...
383
384
        return $this;
385
    }
386
387
    /**
388
     * Iterate over each item in the collection, calling $callback on it. Return false to stop iterating.
389
     *
390
     * @param callable    $callback A callback to use
391
     *
392
     * @return $this
393
     */
394
    public function each(callable $callback)
395
    {
396
        $iter = 0;
397
        foreach ($this as $key => $val) {
398
            if (!$callback($val, $key, $iter++)) {
399
                break;
400
            }
401
        }
402
403
        return $this;
404
    }
405
406
    /**
407
     * Filter the collection.
408
     *
409
     * Using a callback function, this method will filter out unwanted values, returning
410
     * a new collection containing only the values that weren't filtered.
411
     *
412
     * @param callable $predicate The callback function used to filter
0 ignored issues
show
Documentation introduced by
Should the type for parameter $predicate 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...
413
     *
414
     * @return Collection A new collection with only values that weren't filtered
415
     */
416
    public function filter(callable $predicate = null)
417
    {
418
        if (is_null($predicate)) {
419
            $filtered = array_filter($this->data);
420
        } else {
421
            $iter     = 0;
422
            $filtered = [];
423
            foreach($this as $key => $val) {
424
                if ($predicate($val, $key, $iter++)) {
425
                    $filtered[$key] = $val;
426
                }
427
            }
428
        }
429
        return static::factory($filtered);
430
    }
431
432
    /**
433
     * Filter the collection.
434
     *
435
     * Using a callback function, this method will filter out unwanted values, returning
436
     * a new collection containing only the values that weren't filtered.
437
     *
438
     * @param callable $predicate The callback function used to filter
0 ignored issues
show
Documentation introduced by
Should the type for parameter $predicate 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...
439
     *
440
     * @return Collection A new collection with only values that weren't filtered
441
     */
442
    public function exclude(callable $predicate = null)
443
    {
444
        if (is_null($predicate)) {
445
            $predicate = function($val) {
446
                return (bool) $val != false;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
447
            };
448
        }
449
        $iter = 0;
450
        $excluded = [];
451
        foreach ($this as $key => $val) {
452
            if (!$predicate($val, $key, $iter++)) {
453
                $excluded[$key] = $val;
454
            }
455
        }
456
        return static::factory($excluded);
457
    }
458
459
    /**
460
     * Return the first item that meets given criteria.
461
     *
462
     * Using a callback function, this method will return the first item in the collection
463
     * that causes the callback function to return true.
464
     *
465
     * @param callable|null $predicate The callback function
466
     * @param mixed|null    $default   The default return value
467
     *
468
     * @return mixed
469
     */
470
    public function first(callable $predicate = null, $default = null)
471
    {
472
        if (is_null($predicate)) {
473
            if ($this->hasOffset(0)) {
474
                return $this->getOffset(0);
475
            }
476
        } else {
477
            foreach($this as $index => $value) {
478
                if ($predicate($value, $index)) {
479
                    return $value;
480
                }
481
            }
482
        }
483
484
        return $default;
485
    }
486
487
    /**
488
     * Return the last item that meets given criteria.
489
     *
490
     * Using a callback func`tion, this method will return the last item in the collection
491
     * that causes the callback function to return true.
492
     *
493
     * @param callable|null $callback The callback function
494
     * @param mixed|null    $default  The default return value
495
     *
496
     * @return mixed
497
     */
498
    public function last(callable $callback = null, $default = null)
499
    {
500
        $reverse = $this->reverse();
501
//        if (is_null($callback)) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% 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...
502
//            return $reverse->getOffset(0);
503
//        }
504
        return $reverse->first($callback, $default);
505
    }
506
507
    /**
508
     * Returns collection in reverse order.
509
     *
510
     * @return Collection This collection in reverse order.
511
     */
512
    public function reverse()
513
    {
514
        return static::factory(array_reverse($this->getData(), true));
515
    }
516
517
    /**
518
     * Get unique items.
519
     *
520
     * Returns a collection of all the unique items in this collection.
521
     *
522
     * @return Collection This collection with duplicate items removed
523
     */
524
    public function unique()
525
    {
526
        return static::factory(array_unique($this->getData()));
527
    }
528
529
    /**
530
     * Collection factory method.
531
     *
532
     * This method will analyze input data and determine the most appropriate Collection
533
     * class to use. It will then instantiate said Collection class with the given
534
     * data and return it.
535
     *
536
     * @param mixed $data The data to wrap
537
     *
538
     * @return Collection A collection containing $data
539
     */
540
    public static function factory($data = null)
541
    {
542
        return new Collection($data);
543
    }
544
545
    /**
546
     * Determine if structure contains all numeric values.
547
     *
548
     * @return bool
549
     */
550
    public function isNumeric()
551
    {
552
        foreach ($this as $val) {
553
            if (!is_numeric($val)) {
554
                return false;
555
            }
556
        }
557
        return true;
558
    }
559
560
    /**
561
     * @inheritdoc
562
     */
563
    public function hasOffset($offset)
564
    {
565
        try {
566
            $this->getOffsetKey($offset);
567
            return true;
568
        } catch (OutOfBoundsException $e) {
569
            return false;
570
        }
571
    }
572
573
    /**
574
     * @inheritdoc
575
     */
576
    public function getOffsetKey($offset)
577
    {
578
        $offset = normalize_offset($offset, $this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Noz\Collection>, but the function expects a integer|array|object<Noz\traversable>|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...
579
        if (!is_null($key = $this->fold(function($carry, $val, $key, $iter) use ($offset) {
580
            if ($iter++ === $offset) {
581
                return $key;
582
            }
583
            return $carry;
584
        }))) {
585
            return $key;
586
        }
587
        throw new OutOfBoundsException("Offset does not exist: $offset");
588
    }
589
590
    /**
591
     * @inheritdoc
592
     */
593
    public function getOffset($offset)
594
    {
595
        return $this->retrieve($this->getOffsetKey($offset));
596
    }
597
598
    /**
599
     * Get each key/value as an array pair.
600
     *
601
     * Returns a collection of arrays where each item in the collection is [key,value]
602
     *
603
     * @return Collection
604
     */
605
    public function pairs()
606
    {
607
        return static::factory(array_map(
608
            function ($key, $val) {
609
                return [$key, $val];
610
            },
611
            array_keys($this->getData()),
612
            array_values($this->getData())
613
        ));
614
    }
615
616
    // END Iterator methods
617
618
    /**
619
     * Counts how many times each value occurs in a collection.
620
     *
621
     * Returns a new collection with values as keys and how many times that
622
     * value appears in the collection. Works best with scalar values but will
623
     * attempt to work on collections of objects as well.
624
     *
625
     * @return Collection
626
     */
627
    public function frequency()
628
    {
629
        return static::factory($this->fold(
630
            function($carry, $val, $key, $iter) {
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...
631
                if (!isset($carry[$val])) {
632
                    $carry[$val] = 0;
633
                }
634
                $carry[$val]++;
635
                return $carry;
636
            }
637
        ), []);
0 ignored issues
show
Unused Code introduced by
The call to Collection::factory() has too many arguments starting with array().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
638
    }
639
640
    /**
641
     * @inheritDoc
642
     */
643
    public function add($index, $value)
644
    {
645
        if (!$this->has($index)) {
646
            return $this->set($index, $value);
647
        }
648
        return static::factory($this);
649
    }
650
651
    /**
652
     * @inheritdoc
653
     * @todo Maybe read would be a better name for this?
654
     */
655
    public function get($index, $default = null)
656
    {
657
        try {
658
            return $this->retrieve($index);
659
        } catch (OutOfBoundsException $e) {
660
            return $default;
661
        }
662
    }
663
664
    /**
665
     * @inheritdoc
666
     * @todo Maybe read would be a better name for this?
667
     */
668
    public function retrieve($index)
669
    {
670
        if (!$this->has($index)) {
671
            throw new OutOfBoundsException(__CLASS__ . ' could not retrieve value at index ' . $index);
672
        }
673
        return $this->getData()[$index];
674
    }
675
676
    /**
677
     * @inheritDoc
678
     */
679
    public function prepend($item)
680
    {
681
        $data = $this->getData();
682
        array_unshift($data, $item);
683
684
        return static::factory($data);
685
    }
686
687
    /**
688
     * @inheritDoc
689
     */
690
    public function append($item)
691
    {
692
        $data = $this->getData();
693
        array_push($data, $item);
694
695
        return static::factory($data);
696
    }
697
698
    /**
699
     * @inheritDoc
700
     */
701
    public function chunk($size)
702
    {
703
        return static::factory($this->fold(function($chunks, $val, $key, $iter) use ($size) {
704
            if (is_null($chunks)) {
705
                $chunks = [];
706
            }
707
            if ($iter % $size == 0) {
708
                // start new chunk
709
                array_push($chunks, []);
710
            }
711
            $chunk = array_pop($chunks);
712
            array_push($chunk, $val);
713
            array_push($chunks, $chunk);
714
715
            return $chunks;
716
        }));
717
    }
718
719
    public function combine($values)
720
    {
721
        if (!is_traversable($values)) {
722
            throw new InvalidArgumentException(sprintf(
723
                'Expecting traversable data for %s but got %s.',
724
                __METHOD__,
725
                typeof($values)
726
            ));
727
        }
728
        return static::factory(
729
            array_combine(
730
                $this->keys()->toArray(),
731
                with(new Collection($values))->values()->toArray()
732
            )
733
        );
734
    }
735
736
    /**
737
     * @inheritDoc
738
     */
739
    public function diff($data, callable $equals = null)
740
    {
741
        $diff = [];
742
        $orig = $this->getData();
743
        $cmpr = to_array($data);
744
745
        if (is_null ($equals)) {
746
            $equals = function($a, $b) {
747
                return $a == $b;
748
            };
749
        }
750
        foreach ($orig as $key => $val) {
751
            foreach ($cmpr as $cmpKey => $cmpVal) {
752
                if (!$equals($val, $cmpVal)) {
753
                    $diff[$cmpKey] = $cmpVal;
754
                }
755
            }
756
        }
757
        return $diff;
758
    }
759
760
    /**
761
     * @inheritDoc
762
     */
763
    public function diffKeys($data, callable $equals = null)
764
    {
765
        return static::factory(
766
            array_diff_key(
767
                $this->getData(),
768
                to_array($data)
769
            )
770
        );
771
    }
772
773
    /**
774
     * @inheritDoc
775
     */
776
    public function nth($nth, $offset = null)
777
    {
778
        return $this->slice($offset)->filter(function($val, $key, $iter) use ($nth) {
779
            return $iter % $nth == 0;
780
        });
781
    }
782
783
    /**
784
     * @inheritDoc
785
     */
786
    public function except($indexes)
787
    {
788
        return $this->diffKeys(static::factory($indexes)->flip());
789
    }
790
791
    /**
792
     * @inheritDoc
793
     */
794
    public function flip()
795
    {
796
        return static::factory(array_flip($this->getData()));
797
    }
798
799
    /**
800
     * @inheritDoc
801
     */
802
    public function intersect($data)
803
    {
804
        return static::factory(
805
            array_intersect(
806
                $this->toArray(),
807
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 802 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...
808
            )
809
        );
810
    }
811
812
    /**
813
     * @inheritDoc
814
     */
815
    public function intersectKeys($data)
816
    {
817
        return static::factory(
818
            array_intersect_key(
819
                $this->toArray(),
820
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 815 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...
821
            )
822
        );
823
    }
824
825
    /**
826
     * @inheritDoc
827
     */
828
    public function isEmpty(callable $callback = null)
829
    {
830
        if (!is_null($callback)) {
831
            return $this->all($callback);
832
        }
833
        return empty($this->getData());
834
    }
835
836
    /**
837
     * @inheritDoc
838
     */
839
    public function only($indices)
840
    {
841
        return $this->intersectKeys(static::factory($indices)->flip()->toArray());
842
    }
843
844
    /**
845
     * @inheritDoc
846
     */
847
    public function pipe(callable $callback)
848
    {
849
        return $callback($this);
850
    }
851
852
    /**
853
     * @inheritDoc
854
     */
855
    public function random($num)
856
    {
857
        return $this->shuffle()->slice(0, $num);
858
    }
859
860
    /**
861
     * @inheritDoc
862
     */
863
    public function indicesOf($value)
864
    {
865
        return $this->filter(function($val) use ($value) {
866
            return $val == $value;
867
        })->map(function($val, $key) {
868
            return $key;
869
        });
870
    }
871
872
    /**
873
     * @inheritDoc
874
     */
875
    public function shuffle()
876
    {
877
        return static::factory(shuffle($this->getData()));
0 ignored issues
show
Bug introduced by
$this->getData() cannot be passed to shuffle() as the parameter $array expects a reference.
Loading history...
878
    }
879
880
    /**
881
     * @inheritDoc
882
     */
883
    public function slice($offset, $length = null)
884
    {
885
        return static::factory(array_slice($this->getData(), $offset, $length, true));
886
    }
887
888
    /**
889
     * @inheritDoc
890
     */
891
    public function split($num)
892
    {
893
        $count = $this->count();
894
        $size = (int)($count / $num);
895
        $mod = $count % $num;
896
        return static::factory($this->fold(function($chunks, $val, $key, $iter) use ($num, $size, $mod) {
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...
897
            $chunk_count = count($chunks);
898
            if ($chunk_count <= $mod) {
899
                $size++;
900
            }
901
            if (is_null($chunks)) {
902
                // create initial chunks array...
903
                $chunks = [[]];
904
            }
905
906
            // grab the most recent chunk
907
            $chunk = array_pop($chunks);
908
909
            if (count($chunk) < $size) {
910
                // if chunk is less than the expected size, add value to it
911
                array_push($chunk, $val);
912
                array_push($chunks, $chunk);
913
            } else {
914
                // otherwise, add the chunk back and add the value to a new chunk
915
                array_push($chunks, $chunk);
916
                if ($chunk_count <= $num) {
917
                    $chunk = [$val];
918
                }
919
                array_push($chunks, $chunk);
920
            }
921
922
            return $chunks;
923
        }));
924
    }
925
926
    /**
927
     * @inheritDoc
928
     */
929
    public function union($data)
930
    {
931
        return static::factory(
932
            array_merge(
933
                $this->getData(),
934
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 929 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...
935
            )
936
        );
937
    }
938
939
    /**
940
     * @inheritDoc
941
     */
942
    public function zip(...$data)
943
    {
944
        /** @var CollectionInterface $args The function arguments TODO: Change this to SequenceInterface when you have that interface. */
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...
945
        $args = new Sequence(func_get_args());
946
        $args->map(function($val) {
947
            if (is_arrayable($val)) {
948
                return to_array($val);
949
            } else {
950
                // @todo throw exception?
951
                return [];
952
            }
953
        });
954
        $args = $args->prepend($this->getData())
955
                     ->prepend(null);
956
957
        return static::factory(
958
//            array_map(
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...
959
//                null,
960
//                $this->getData(),
961
//                ...$data
962
//            )
963
            call_user_func_array(
964
                'array_map',
965
                $args->toArray()
966
            )
967
        );
968
    }
969
970
    /**
971
     * @inheritDoc
972
     */
973
    public function fold(callable $callback, $initial = null)
974
    {
975
        $iter = 0;
976
        $carry = $initial;
977
        foreach ($this as $key => $val) {
978
            $carry = $callback($carry, $val, $key, $iter++);
979
        }
980
        return $carry;
981
    }
982
983
    /**
984
     * @inheritDoc
985
     */
986
    public function foldl(callable $callback, $initial = null)
987
    {
988
        return $this->reverse()->fold($callback, $initial);
989
    }
990
991
    /**
992
     * @inheritDoc
993
     */
994
    public function all(callable $callback = null)
995
    {
996
        if (is_null($callback)) {
997
            $callback = function($val) {
998
                return (bool) $val;
999
            };
1000
        }
1001
        return $this->filter($callback)->isEmpty();
1002
    }
1003
1004
    /**
1005
     * @inheritDoc
1006
     */
1007
    public function none(callable $callback = null)
1008
    {
1009
        if (is_null($callback)) {
1010
            $callback = function($val) {
1011
                return (bool) $val;
1012
            };
1013
        }
1014
        return $this->filter($callback)->isEmpty();
1015
    }
1016
1017
    // BEGIN Numeric Collection Methods
1018
    // These methods only really work on numeric data.
1019
1020
    /**
1021
     * Increment an item.
1022
     *
1023
     * Increment the item specified by $key by one value. Intended for integers
1024
     * but also works (using this term loosely) for letters. Any other data type
1025
     * it may modify is unintended behavior at best.
1026
     *
1027
     * This method modifies its internal data array rather than returning a new
1028
     * collection.
1029
     *
1030
     * @param mixed $index    The key of the item you want to increment.
1031
     * @param int   $interval The interval that $key should be incremented by
1032
     *
1033
     * @return Collection
1034
     */
1035
    public function increment($index, $interval = 1)
1036
    {
1037
        $val = $this->retrieve($index);
1038
        $val += $interval;
1039
        return $this->set($index, $val);
1040
    }
1041
1042
    /**
1043
     * Decrement an item.
1044
     *
1045
     * Frcrement the item specified by $key by one value. Intended for integers.
1046
     * Does not work for letters and if it does anything to anything else, it's
1047
     * unintended at best.
1048
     *
1049
     * This method modifies its internal data array rather than returning a new
1050
     * collection.
1051
     *
1052
     * @param mixed $index      The key of the item you want to decrement.
1053
     * @param int   $interval The interval that $key should be decremented by
1054
     *
1055
     * @return Collection
1056
     */
1057
    public function decrement($index, $interval = 1)
1058
    {
1059
        $val = $this->retrieve($index);
1060
        $val -= $interval;
1061
        return $this->set($index, $val);
1062
    }
1063
1064
    /**
1065
     * Get the sum.
1066
     *
1067
     * @return int|float The sum of all values in collection
1068
     */
1069
    public function sum()
1070
    {
1071
        return array_sum($this->toArray());
1072
    }
1073
1074
    /**
1075
     * Get the average.
1076
     *
1077
     * @return float|int The average value from the collection
1078
     */
1079
    public function average()
1080
    {
1081
        return $this->sum() / $this->count();
1082
    }
1083
1084
    /**
1085
     * Get the mode.
1086
     *
1087
     * @return float|int The mode
1088
     */
1089
    public function mode()
1090
    {
1091
        $counts = $this->counts()->toArray();
1092
        arsort($counts);
1093
        $mode = key($counts);
1094
1095
        return (strpos($mode, '.')) ? floatval($mode) : intval($mode);
1096
    }
1097
1098
    /**
1099
     * Get the median value.
1100
     *
1101
     * @return float|int The median value
1102
     */
1103
    public function median()
1104
    {
1105
        $count = $this->count();
1106
        $data  = $this->toArray();
1107
        natcasesort($data);
1108
        $middle = $count / 2;
1109
        $values = array_values($data);
1110
        if ($count % 2 == 0) {
1111
            // even number, use middle
1112
            $low  = $values[$middle - 1];
1113
            $high = $values[$middle];
1114
1115
            return ($low + $high) / 2;
1116
        }
1117
        // odd number return median
1118
        return $values[$middle];
1119
    }
1120
1121
    /**
1122
     * Get the maximum value.
1123
     *
1124
     * @return mixed The maximum
1125
     */
1126
    public function max()
1127
    {
1128
        return max($this->getData());
1129
    }
1130
1131
    /**
1132
     * Get the minimum value.
1133
     *
1134
     * @return mixed The minimum
1135
     */
1136
    public function min()
1137
    {
1138
        return min($this->getData());
1139
    }
1140
1141
    /**
1142
     * Get the number of times each item occurs in the collection.
1143
1144
     * This method will return a NumericCollection where keys are the
1145
     * values and values are the number of times that value occurs in
1146
     * the original collection.
1147
1148
     * @return Collection
1149
     */
1150
    public function counts()
1151
    {
1152
        return static::factory(array_count_values($this->toArray()));
1153
    }
1154
1155
    /**
1156
     * @param $serialized
1157
     */
1158
    public function unserialize($serialized)
1159
    {
1160
        $this->setData(unserialize($serialized));
1161
    }
1162
1163
}
1164