GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

collection_functions.php ➔ combine()   A
last analyzed

Complexity

Conditions 3
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
nc 1
nop 2
dl 0
loc 19
ccs 11
cts 11
cp 1
crap 3
rs 9.6333
c 0
b 0
f 0
1
<?php
2
3
namespace DusanKasan\Knapsack;
4
5
use DusanKasan\Knapsack\Exceptions\InvalidArgument;
6
use DusanKasan\Knapsack\Exceptions\ItemNotFound;
7
use DusanKasan\Knapsack\Exceptions\NoMoreItems;
8
use Iterator;
9
use IteratorIterator;
10
use Traversable;
11
12
/**
13
 * Converts $collection to array. If there are multiple items with the same key, only the last will be preserved.
14
 *
15
 * @param array|Traversable $collection
16
 * @return array
17
 */
18
function toArray($collection)
19
{
20 71
    return is_array($collection) ? $collection : iterator_to_array($collection);
21
}
22
23
/**
24
 * Returns a lazy collection of distinct items in $collection.
25
 *
26
 * @param array|Traversable $collection
27
 * @return Collection
28
 */
29
function distinct($collection)
30
{
31
    $generatorFactory = function () use ($collection) {
32 1
        $distinctValues = [];
33
34 1
        foreach ($collection as $key => $value) {
35 1
            if (!in_array($value, $distinctValues)) {
36 1
                $distinctValues[] = $value;
37 1
                yield $key => $value;
38 1
            }
39 1
        }
40 1
    };
41
42 1
    return new Collection($generatorFactory);
43
}
44
45
/**
46
 * Returns number of items in $collection.
47
 *
48
 * @param array|Traversable $collection
49
 * @return int
50
 */
51
function size($collection)
52
{
53 8
    $result = 0;
54 8
    foreach ($collection as $value) {
55 8
        $result++;
56 8
    }
57
58 8
    return $result;
59
}
60
61
/**
62
 * Returns a non-lazy collection with items from $collection in reversed order.
63
 *
64
 * @param array|Traversable $collection
65
 * @return Collection
66
 */
67
function reverse($collection)
68
{
69
    $generatorFactory = function () use ($collection) {
70 4
        $array = [];
71 4
        foreach ($collection as $key => $value) {
72 3
            $array[] = [$key, $value];
73 4
        }
74
75 4
        return map(
76 4
            indexBy(
77 4
                array_reverse($array),
78
                function ($item) {
79 3
                    return $item[0];
80
                }
81 4
            ),
82
            function ($item) {
83 3
                return $item[1];
84
            }
85 4
        );
86 4
    };
87
88 4
    return new Collection($generatorFactory);
89
}
90
91
/**
92
 * Returns a lazy collection of values from $collection (i.e. the keys are reset).
93
 *
94
 * @param array|Traversable $collection
95
 * @return Collection
96
 */
97
function values($collection)
98
{
99
    $generatorFactory = function () use ($collection) {
100 32
        foreach ($collection as $value) {
101 30
            yield $value;
102 26
        }
103 32
    };
104
105 32
    return new Collection($generatorFactory);
106 1
}
107
108
/**
109
 * Returns a lazy collection of keys from $collection.
110
 *
111
 * @param array|Traversable $collection
112
 * @return Collection
113
 */
114
function keys($collection)
115
{
116
    $generatorFactory = function () use ($collection) {
117 1
        foreach ($collection as $key => $value) {
118 1
            yield $key;
119 1
        }
120 1
    };
121
122 1
    return new Collection($generatorFactory);
123
}
124
125
/**
126
 * Returns a lazy collection of items from $collection repeated infinitely.
127
 *
128
 * @param array|Traversable $collection
129
 * @return Collection
130
 */
131
function cycle($collection)
132
{
133
    $generatorFactory = function () use ($collection) {
134 1
        while (true) {
135 1
            foreach ($collection as $key => $value) {
136 1
                yield $key => $value;
137 1
            }
138 1
        }
139 1
    };
140
141 1
    return new Collection($generatorFactory);
142
}
143
144
/**
145
 * Returns a non-lazy collection of shuffled items from $collection.
146
 *
147
 * @param array|Traversable $collection
148
 * @return Collection
149
 */
150
function shuffle($collection)
151
{
152 1
    $buffer = [];
153 1
    foreach ($collection as $key => $value) {
154 1
        $buffer[] = [$key, $value];
155 1
    }
156
157 1
    \shuffle($buffer);
158
159 1
    return dereferenceKeyValue($buffer);
160
}
161
162
/**
163
 * Returns true if $collection does not contain any items.
164
 *
165
 * @param array|Traversable $collection
166
 * @return bool
167
 */
168
function isEmpty($collection)
169
{
170 3
    foreach ($collection as $value) {
171 1
        return false;
172 2
    }
173
174 2
    return true;
175
}
176
177
/**
178
 * Returns true if $collection does contain any items.
179
 *
180
 * @param array|Traversable $collection
181
 * @return bool
182
 */
183
function isNotEmpty($collection)
184
{
185 2
    return !isEmpty($collection);
186
}
187
188
/**
189
 * Returns a collection where keys are distinct values from $collection and values are number of occurrences of each
190
 * value.
191
 *
192
 * @param array|Traversable $collection
193
 * @return Collection
194
 */
195
function frequencies($collection)
196
{
197 1
    return countBy($collection, '\DusanKasan\Knapsack\identity');
198
}
199
200
/**
201
 * Returns the first item of $collection or throws ItemNotFound if #collection is empty.
202
 *
203
 * @param array|Traversable $collection
204
 * @return mixed
205
 */
206
function first($collection)
207
{
208 13
    return get(values($collection), 0);
209
}
210
211
/**
212
 * Returns the last item of $collection or throws ItemNotFound if #collection is empty.
213
 *
214
 * @param array|Traversable $collection
215
 * @return mixed
216
 */
217
function last($collection)
218
{
219 2
    return first(reverse($collection));
220
}
221
222
/**
223
 * Returns a lazy collection of items of $collection where value of each item is set to the return value of calling
224
 * $function on its value and key.
225
 *
226
 * @param array|Traversable $collection
227
 * @param callable $function ($value, $key)
228
 * @return Collection
229
 */
230 View Code Duplication
function map($collection, callable $function)
231
{
232
    $generatorFactory = function () use ($collection, $function) {
233 19
        foreach ($collection as $key => $value) {
234 18
            yield $key => $function($value, $key);
235 18
        }
236 19
    };
237
238 19
    return new Collection($generatorFactory);
239
}
240
241
/**
242
 * Returns a lazy collection of items from $collection for which $function returns true.
243
 *
244
 * @param array|Traversable $collection
245
 * @param callable|null $function ($value, $key)
246
 * @return Collection
247
 */
248
function filter($collection, callable $function = null)
249
{
250 7
    if (null === $function) {
251
        $function = function ($value) {
252 1
            return (bool) $value;
253 1
        };
254 1
    }
255
256
    $generatorFactory = function () use ($collection, $function) {
257 7
        foreach ($collection as $key => $value) {
258 7
            if ($function($value, $key)) {
259 5
                yield $key => $value;
260 5
            }
261 7
        }
262 7
    };
263
264 7
    return new Collection($generatorFactory);
265
}
266
267
/**
268
 * Returns a lazy collection with items from all $collections passed as argument appended together
269
 *
270
 * @param array|Traversable ...$collections
271
 * @return Collection
272
 */
273
function concat(...$collections)
274
{
275
    $generatorFactory = function () use ($collections) {
276 5
        foreach ($collections as $collection) {
277 5
            foreach ($collection as $key => $value) {
278 5
                yield $key => $value;
279 5
            }
280 5
        }
281 5
    };
282
283 5
    return new Collection($generatorFactory);
284
}
285
286
/**
287
 * Reduces the collection to single value by iterating over the collection and calling $reduction while
288
 * passing $startValue and current key/item as parameters. The output of $function is used as $startValue in
289
 * next iteration. The output of $function on last element is the return value of this function.
290
 *
291
 * @param array|Traversable $collection
292
 * @param callable $function ($value, $key)
293
 * @param mixed $startValue
294
 * @return mixed
295
 */
296
function reduce($collection, callable $function, $startValue)
297
{
298 3
    $tmp = duplicate($startValue);
299
300 3
    foreach ($collection as $key => $value) {
301 3
        $tmp = $function($tmp, $value, $key);
302 3
    }
303
304 3
    return $tmp;
305
}
306
307
/**
308
 * Flattens multiple levels of nesting in collection. If $levelsToFlatten is not specified, flattens all levels of
309
 * nesting.
310
 *
311
 * @param array|Traversable $collection
312
 * @param int $levelsToFlatten -1 to flatten everything
313
 * @return Collection
314
 */
315
function flatten($collection, $levelsToFlatten = -1)
316
{
317
    $generatorFactory = function () use ($collection, $levelsToFlatten) {
318 3
        $flattenNextLevel = $levelsToFlatten < 0 || $levelsToFlatten > 0;
319 3
        $childLevelsToFlatten = $levelsToFlatten > 0 ? $levelsToFlatten - 1 : $levelsToFlatten;
320
321 3
        foreach ($collection as $key => $value) {
322 3
            if ($flattenNextLevel && (is_array($value) || $value instanceof Traversable)) {
323 3
                foreach (flatten($value, $childLevelsToFlatten) as $childKey => $childValue) {
324 3
                    yield $childKey => $childValue;
325 3
                }
326 3
            } else {
327 3
                yield $key => $value;
328
            }
329 3
        }
330 3
    };
331
332 3
    return new Collection($generatorFactory);
333
}
334
335
/**
336
 * Returns a non-lazy collection sorted using $collection($item1, $item2, $key1, $key2 ). $collection should
337
 * return true if first item is larger than the second and false otherwise.
338
 *
339
 * @param array|Traversable $collection
340
 * @param callable $function ($value1, $value2, $key1, $key2)
341
 * @return Collection
342
 */
343
function sort($collection, callable $function)
344
{
345 2
    $array = iterator_to_array(
346 2
        values(
347 2
            map(
348 2
                $collection,
349
                function ($value, $key) {
350 2
                    return [$key, $value];
351
                }
352 2
            )
353 2
        )
354 2
    );
355
356 2
    uasort(
357 2
        $array,
358
        function ($a, $b) use ($function) {
359 2
            return $function($a[1], $b[1], $a[0], $b[0]);
360
        }
361 2
    );
362
363 2
    return dereferenceKeyValue($array);
364
}
365
366
/**
367
 * Returns a lazy collection that is a part of $collection starting from $from position and ending in $to position.
368
 * If $to is not provided, the returned collection is contains all items from $from until end of $collection. All items
369
 * before $from are iterated over, but not included in result.
370
 *
371
 * @param array|Traversable $collection
372
 * @param int $from
373
 * @param int $to -1 to slice until end
374
 * @return Collection
375
 */
376
function slice($collection, $from, $to = -1)
377
{
378
    $generatorFactory = function () use ($collection, $from, $to) {
379 15
        $index = 0;
380 15
        foreach ($collection as $key => $value) {
381 15
            if ($index >= $from && ($index < $to || $to == -1)) {
382 15
                yield $key => $value;
383 15
            } elseif ($index >= $to && $to >= 0) {
384 12
                break;
385
            }
386
387 15
            $index++;
388 15
        }
389 15
    };
390
391 15
    return new Collection($generatorFactory);
392
}
393
394
/**
395
 * Returns a non-lazy collection of items grouped by the result of $function.
396
 *
397
 * @param array|Traversable $collection
398
 * @param callable $function ($value, $key)
399
 * @return Collection
400
 */
401
function groupBy($collection, callable $function)
402
{
403 4
    $result = [];
404
405 4
    foreach ($collection as $key => $value) {
406 4
        $newKey = $function($value, $key);
407
408 4
        $result[$newKey][] = $value;
409 4
    }
410
411 4
    return Collection::from($result)
412
        ->map(function ($entry) {
413 4
            return new Collection($entry);
414 4
        });
415
}
416
417
/**
418
 * Returns a non-lazy collection of items grouped by the value at given key. Ignores non-collection items and items
419
 * without the given keys
420
 *
421
 * @param array|Traversable $collection
422
 * @param mixed $key
423
 * @return Collection
424
 */
425
function groupByKey($collection, $key)
426
{
427
    $generatorFactory = function () use ($collection, $key) {
428
429 1
        return groupBy(
430 1
            filter(
431 1
                $collection,
432
                function ($item) use ($key) {
433 1
                    return isCollection($item) && has($item, $key);
434
                }
435 1
            ),
436
            function ($value) use ($key) {
437 1
                return get($value, $key);
438
            }
439 1
        );
440 1
    };
441
442 1
    return new Collection($generatorFactory);
443
}
444
/**
445
 * Executes $function for each item in $collection
446
 *
447
 * @param array|Traversable $collection
448
 * @param callable $function ($value, $key)
449
 * @return Collection
450
 */
451 View Code Duplication
function each($collection, callable $function)
452
{
453
    $generatorFactory = function () use ($collection, $function) {
454 1
        foreach ($collection as $key => $value) {
455 1
            $function($value, $key);
456
457 1
            yield $key => $value;
458 1
        }
459 1
    };
460
461 1
    return new Collection($generatorFactory);
462
}
463
464
/**
465
 * Returns an item with $key key from $collection. If that key is not present, throws ItemNotFound.
466
 *
467
 * @param array|Traversable $collection
468
 * @param mixed $key
469
 * @return mixed
470
 */
471
function get($collection, $key)
472
{
473 20
    foreach ($collection as $valueKey => $value) {
474 18
        if ($key === $valueKey) {
475 17
            return $value;
476
        }
477 16
    }
478
479 9
    throw new ItemNotFound;
480
}
481
482
/**
483
 * Returns an item with $key key from $collection. If that key is not present, returns $default.
484
 *
485
 * @param array|Traversable $collection
486
 * @param mixed $key
487
 * @param mixed $default value returned if key is not found
488
 * @return mixed
489
 */
490
function getOrDefault($collection, $key, $default)
491
{
492
    try {
493 3
        return get($collection, $key);
494 3
    } catch (ItemNotFound $e) {
495 3
        return $default;
496
    }
497
}
498
499
/**
500
 * Returns the first item from $collection for which $function returns true. If item like that is not present, returns
501
 * $default.
502
 *
503
 * @param array|Traversable $collection
504
 * @param callable $function ($value, $key)
505
 * @param mixed $default
506
 * @return mixed
507
 */
508
function find($collection, callable $function, $default = null)
509
{
510 1
    foreach ($collection as $key => $value) {
511 1
        if ($function($value, $key)) {
512 1
            return $value;
513
        }
514 1
    }
515
516 1
    return $default;
517
}
518
519
/**
520
 * Returns a lazy collection by changing keys of $collection for each item to the result of $function for
521
 * that item.
522
 *
523
 * @param array|Traversable $collection
524
 * @param callable $function ($value, $key)
525
 * @return Collection
526
 */
527 View Code Duplication
function indexBy($collection, callable $function)
528
{
529
    $generatorFactory = function () use ($collection, $function) {
530 5
        foreach ($collection as $key => $value) {
531 4
            yield $function($value, $key) => $value;
532 4
        }
533 5
    };
534
535 5
    return new Collection($generatorFactory);
536
}
537
538
/**
539
 * Returns a non-lazy collection of items whose keys are the return values of $function and values are the number of
540
 * items in this collection for which the $function returned this value.
541
 *
542
 * @param array|Traversable $collection
543
 * @param callable $function ($value, $key)
544
 * @return Collection
545
 */
546
function countBy($collection, callable $function)
547
{
548 2
    return map(
549 2
        groupBy($collection, $function),
550
        '\DusanKasan\Knapsack\size'
551 2
    );
552
}
553
554
/**
555
 * Returns true if $function returns true for every item in $collection
556
 *
557
 * @param array|Traversable $collection
558
 * @param callable $function ($value, $key)
559
 * @return bool
560
 */
561
function every($collection, callable $function)
562
{
563 1
    foreach ($collection as $key => $value) {
564 1
        if (!$function($value, $key)) {
565 1
            return false;
566
        }
567 1
    }
568
569 1
    return true;
570
}
571
572
/**
573
 * Returns true if $function returns true for at least one item in $collection.
574
 *
575
 * @param array|Traversable $collection
576
 * @param callable $function ($value, $key)
577
 * @return bool
578
 */
579
function some($collection, callable $function)
580
{
581 4
    foreach ($collection as $key => $value) {
582 4
        if ($function($value, $key)) {
583 2
            return true;
584
        }
585 3
    }
586
587 3
    return false;
588
}
589
590
/**
591
 * Returns true if $needle is found in $collection values.
592
 *
593
 * @param array|Traversable $collection
594
 * @param mixed $needle
595
 * @return bool
596
 */
597
function contains($collection, $needle)
598
{
599 1
    foreach ($collection as $key => $value) {
600 1
        if ($value === $needle) {
601 1
            return true;
602
        }
603 1
    }
604
605 1
    return false;
606
}
607
608
/**
609
 * Reduce that walks from right to the left.
610
 *
611
 * @param array|Traversable $collection
612
 * @param callable $function
613
 * @param mixed $startValue
614
 * @return mixed
615
 */
616
function reduceRight($collection, callable $function, $startValue)
617
{
618 1
    return reduce(reverse($collection), $function, $startValue);
619
}
620
621
/**
622
 * Returns a lazy collection of first $numberOfItems items of $collection.
623
 *
624
 * @param array|Traversable $collection
625
 * @param int $numberOfItems
626
 * @return Collection
627
 */
628
function take($collection, $numberOfItems)
629
{
630 12
    return slice($collection, 0, $numberOfItems);
631
}
632
633
/**
634
 * Returns a lazy collection of all but first $numberOfItems items of $collection.
635
 *
636
 * @param array|Traversable $collection
637
 * @param int $numberOfItems
638
 * @return Collection
639
 */
640
function drop($collection, $numberOfItems)
641
{
642 4
    return slice($collection, $numberOfItems);
643
}
644
645
/**
646
 * Returns a lazy collection of values, where first value is $value and all subsequent values are computed by applying
647
 * $function to the last value in the collection. By default this produces an infinite collection. However you can
648
 * end the collection by throwing a NoMoreItems exception.
649
 *
650
 * @param mixed $value
651
 * @param callable $function ($value, $key)
652
 * @return Collection
653
 */
654
function iterate($value, callable $function)
655
{
656 4
    $duplicated = duplicate($value);
657
    $generatorFactory = function () use ($duplicated, $function) {
658 4
        $value = $duplicated;
659
660 4
        yield $value;
661
662 4
        while (true) {
663
            try {
664 4
                $value = $function($value);
665 4
                yield $value;
666 4
            } catch (NoMoreItems $e) {
667 2
                break;
668
            }
669 4
        }
670 4
    };
671
672 4
    return new Collection($generatorFactory);
673
}
674
675
/**
676
 * Returns a lazy collection of items from $collection for which $function returned true.
677
 *
678
 * @param array|Traversable $collection
679
 * @param callable $function ($value, $key)
680
 * @return Collection
681
 */
682
function reject($collection, callable $function)
683
{
684 2
    return filter(
685 2
        $collection,
686
        function ($value, $key) use ($function) {
687 2
            return !$function($value, $key);
688
        }
689 2
    );
690
}
691
692
/**
693
 * Returns a lazy collection of items in $collection without the last $numberOfItems items.
694
 *
695
 * @param array|Traversable $collection
696
 * @param int $numberOfItems
697
 * @return Collection
698
 */
699
function dropLast($collection, $numberOfItems = 1)
700
{
701
    $generatorFactory = function () use ($collection, $numberOfItems) {
702 1
        $buffer = [];
703
704 1
        foreach ($collection as $key => $value) {
705 1
            $buffer[] = [$key, $value];
706
707 1
            if (count($buffer) > $numberOfItems) {
708 1
                $val = array_shift($buffer);
709 1
                yield $val[0] => $val[1];
710 1
            }
711 1
        }
712 1
    };
713
714 1
    return new Collection($generatorFactory);
715
}
716
717
/**
718
 * Returns a lazy collection of items from $collection separated by $separator.
719
 *
720
 * @param array|Traversable $collection
721
 * @param mixed $separator
722
 * @return Collection
723
 */
724
function interpose($collection, $separator)
725
{
726
    $generatorFactory = function () use ($collection, $separator) {
727 1
        foreach (take($collection, 1) as $key => $value) {
728 1
            yield $key => $value;
729 1
        }
730
731 1
        foreach (drop($collection, 1) as $key => $value) {
732 1
            yield $separator;
733 1
            yield $key => $value;
734 1
        }
735 1
    };
736
737 1
    return new Collection($generatorFactory);
738
}
739
740
/**
741
 * Returns a lazy collection of first item from first collection, first item from second, second from first and
742
 * so on. Accepts any number of collections.
743
 *
744
 * @param array|Traversable ...$collections
745
 * @return Collection
746
 */
747
function interleave(...$collections)
748
{
749
    $generatorFactory = function () use ($collections) {
750
        /* @var Iterator[] $iterators */
751 1
        $iterators = array_map(
752
            function ($collection) {
753 1
                $it = new IteratorIterator(new Collection($collection));
754 1
                $it->rewind();
755 1
                return $it;
756 1
            },
757
            $collections
758 1
        );
759
760
        do {
761 1
            $valid = false;
762 1
            foreach ($iterators as $it) {
763 1
                if ($it->valid()) {
764 1
                    yield $it->key() => $it->current();
765 1
                    $it->next();
766 1
                    $valid = true;
767 1
                }
768 1
            }
769 1
        } while ($valid);
770 1
    };
771
772 1
    return new Collection($generatorFactory);
773
}
774
775
/**
776
 * Returns a lazy collection of items in $collection with $value added as first element. If $key is not provided
777
 * it will be next integer index.
778
 *
779
 * @param array|Traversable $collection
780
 * @param mixed $value
781
 * @param mixed|null $key
782
 * @return Collection
783
 */
784 View Code Duplication
function prepend($collection, $value, $key = null)
785
{
786
    $generatorFactory = function () use ($collection, $value, $key) {
787 2
        if ($key === null) {
788 1
            yield $value;
789 1
        } else {
790 1
            yield $key => $value;
791
        }
792
793 2
        foreach ($collection as $key => $value) {
794 2
            yield $key => $value;
795 2
        }
796 2
    };
797
798 2
    return new Collection($generatorFactory);
799
}
800
801
/**
802
 * Returns a lazy collection of items in $collection with $value added as last element. If $key is not provided
803
 * it will be next integer index.
804
 *
805
 * @param array|Traversable $collection
806
 * @param mixed $value
807
 * @param mixed|null $key
808
 * @return Collection
809
 */
810 View Code Duplication
function append($collection, $value, $key = null)
811
{
812
    $generatorFactory = function () use ($collection, $value, $key) {
813 7
        foreach ($collection as $k => $v) {
814 7
            yield $k => $v;
815 7
        }
816
817 7
        if ($key === null) {
818 5
            yield $value;
819 5
        } else {
820 2
            yield $key => $value;
821
        }
822 7
    };
823
824 7
    return new Collection($generatorFactory);
825
}
826
827
/**
828
 * Returns a lazy collection by removing items from $collection until first item for which $function returns false.
829
 *
830
 * @param array|Traversable $collection
831
 * @param callable $function ($value, $key)
832
 * @return Collection
833
 */
834 View Code Duplication
function dropWhile($collection, callable $function)
835
{
836
    $generatorFactory = function () use ($collection, $function) {
837 2
        $shouldDrop = true;
838 2
        foreach ($collection as $key => $value) {
839 2
            if ($shouldDrop) {
840 2
                $shouldDrop = $function($value, $key);
841 2
            }
842
843 2
            if (!$shouldDrop) {
844 2
                yield $key => $value;
845 2
            }
846 2
        }
847 2
    };
848
849 2
    return new Collection($generatorFactory);
850
}
851
852
/**
853
 * Returns a lazy collection of items from $collection until first item for which $function returns false.
854
 *
855
 * @param array|Traversable $collection
856
 * @param callable $function ($value, $key)
857
 * @return Collection
858
 */
859 View Code Duplication
function takeWhile($collection, callable $function)
860
{
861
    $generatorFactory = function () use ($collection, $function) {
862 2
        $shouldTake = true;
863 2
        foreach ($collection as $key => $value) {
864 2
            if ($shouldTake) {
865 2
                $shouldTake = $function($value, $key);
866 2
            }
867
868 2
            if ($shouldTake) {
869 2
                yield $key => $value;
870 2
            }
871 2
        }
872 2
    };
873
874 2
    return new Collection($generatorFactory);
875
}
876
877
/**
878
 * Returns a lazy collection. A result of calling map and flatten(1)
879
 *
880
 * @param array|Traversable $collection
881
 * @param callable $function ($value, $key)
882
 * @return Collection
883
 */
884
function mapcat($collection, callable $function)
885
{
886 1
    return flatten(map($collection, $function), 1);
887
}
888
889
/**
890
 * Returns a lazy collection [take($collection, $position), drop($collection, $position)]
891
 *
892
 * @param array|Traversable $collection
893
 * @param int $position
894
 * @return Collection
895
 */
896
function splitAt($collection, $position)
897
{
898
    $generatorFactory = function () use ($collection, $position) {
899 1
        yield take($collection, $position);
900 1
        yield drop($collection, $position);
901 1
    };
902
903 1
    return new Collection($generatorFactory);
904
}
905
906
/**
907
 * Returns a lazy collection [takeWhile($collection, $function), dropWhile($collection, $function)]
908
 *
909
 * @param array|Traversable $collection
910
 * @param callable $function ($value, $key)
911
 * @return Collection
912
 */
913
function splitWith($collection, callable $function)
914
{
915
    $generatorFactory = function () use ($collection, $function) {
916 1
        yield takeWhile($collection, $function);
917 1
        yield dropWhile($collection, $function);
918 1
    };
919
920 1
    return new Collection($generatorFactory);
921
}
922
923
/**
924
 * Returns a lazy collection with items from $collection but values that are found in keys of $replacementMap
925
 * are replaced by their values.
926
 *
927
 * @param array|Traversable $collection
928
 * @param array|Traversable $replacementMap
929
 * @return Collection
930
 */
931 View Code Duplication
function replace($collection, $replacementMap)
932
{
933
    $generatorFactory = function () use ($collection, $replacementMap) {
934 1
        foreach ($collection as $key => $value) {
935 1
            $newValue = getOrDefault($replacementMap, $value, $value);
936 1
            yield $key => $newValue;
937 1
        }
938 1
    };
939
940 1
    return new Collection($generatorFactory);
941
}
942
943
/**
944
 * Returns a lazy collection of reduction steps.
945
 *
946
 * @param array|Traversable $collection
947
 * @param callable $function
948
 * @param mixed $startValue
949
 * @return Collection
950
 */
951 View Code Duplication
function reductions($collection, callable $function, $startValue)
952
{
953
    $generatorFactory = function () use ($collection, $function, $startValue) {
954 1
        $tmp = duplicate($startValue);
955
956 1
        yield $tmp;
957 1
        foreach ($collection as $key => $value) {
958 1
            $tmp = $function($tmp, $value, $key);
959 1
            yield $tmp;
960 1
        }
961 1
    };
962
963 1
    return new Collection($generatorFactory);
964
}
965
966
/**
967
 * Returns a lazy collection of every nth ($step) item in  $collection.
968
 *
969
 * @param array|Traversable $collection
970
 * @param int $step
971
 * @return Collection
972
 */
973
function takeNth($collection, $step)
974
{
975
    $generatorFactory = function () use ($collection, $step) {
976 1
        $index = 0;
977 1
        foreach ($collection as $key => $value) {
978 1
            if ($index % $step == 0) {
979 1
                yield $key => $value;
980 1
            }
981
982 1
            $index++;
983 1
        }
984 1
    };
985
986 1
    return new Collection($generatorFactory);
987
}
988
989
/**
990
 * Returns a lazy collection of collections of $numberOfItems items each, at $step step
991
 * apart. If $step is not supplied, defaults to $numberOfItems, i.e. the partitions
992
 * do not overlap. If a $padding collection is supplied, use its elements as
993
 * necessary to complete last partition up to $numberOfItems items. In case there are
994
 * not enough padding elements, return a partition with less than $numberOfItems items.
995
 *
996
 * @param array|Traversable $collection
997
 * @param int $numberOfItems
998
 * @param int $step
999
 * @param array|Traversable $padding
1000
 * @return Collection
1001
 */
1002
function partition($collection, $numberOfItems, $step = -1, $padding = [])
1003
{
1004
    $generatorFactory = function () use ($collection, $numberOfItems, $step, $padding) {
1005 1
        $buffer = [];
1006 1
        $itemsToSkip = 0;
1007 1
        $tmpStep = $step ?: $numberOfItems;
1008
1009 1
        foreach ($collection as $key => $value) {
1010 1
            if (count($buffer) == $numberOfItems) {
1011 1
                yield dereferenceKeyValue($buffer);
1012
1013 1
                $buffer = array_slice($buffer, $tmpStep);
1014 1
                $itemsToSkip = $tmpStep - $numberOfItems;
1015 1
            }
1016
1017 1
            if ($itemsToSkip <= 0) {
1018 1
                $buffer[] = [$key, $value];
1019 1
            } else {
1020 1
                $itemsToSkip--;
1021
            }
1022 1
        }
1023
1024 1
        yield take(
1025 1
            concat(dereferenceKeyValue($buffer), $padding),
1026
            $numberOfItems
1027 1
        );
1028 1
    };
1029
1030 1
    return new Collection($generatorFactory);
1031
}
1032
1033
/**
1034
 * Returns a lazy collection created by partitioning $collection each time $function returned a different value.
1035
 *
1036
 * @param array|Traversable $collection
1037
 * @param callable $function
1038
 * @return Collection
1039
 */
1040
function partitionBy($collection, callable $function)
1041
{
1042
    $generatorFactory = function () use ($collection, $function) {
1043 1
        $result = null;
1044 1
        $buffer = [];
1045
1046 1
        foreach ($collection as $key => $value) {
1047 1
            $newResult = $function($value, $key);
1048
1049 1
            if (!empty($buffer) && $result != $newResult) {
1050 1
                yield dereferenceKeyValue($buffer);
1051 1
                $buffer = [];
1052 1
            }
1053
1054 1
            $result = $newResult;
1055 1
            $buffer[] = [$key, $value];
1056 1
        }
1057
1058 1
        if (!empty($buffer)) {
1059 1
            yield dereferenceKeyValue($buffer);
1060 1
        }
1061 1
    };
1062
1063 1
    return new Collection($generatorFactory);
1064
}
1065
1066
/**
1067
 * Returns a lazy collection of $value repeated $times times. If $times is not provided the collection is infinite.
1068
 *
1069
 * @param mixed $value
1070
 * @param int $times
1071
 * @return Collection
1072
 */
1073
function repeat($value, $times = -1)
1074
{
1075
    $generatorFactory = function () use ($value, $times) {
1076 3
        $tmpTimes = $times;
1077
1078 3
        while ($tmpTimes != 0) {
1079 3
            yield $value;
1080
1081 3
            $tmpTimes = $tmpTimes < 0 ? -1 : $tmpTimes - 1;
1082 3
        }
1083 3
    };
1084
1085 3
    return new Collection($generatorFactory);
1086
}
1087
1088
/**
1089
 * Returns a lazy collection of numbers starting at $start, incremented by $step until $end is reached.
1090
 *
1091
 * @param int $start
1092
 * @param int|null $end
1093
 * @param int $step
1094
 * @return Collection
1095
 */
1096
function range($start = 0, $end = null, $step = 1)
1097
{
1098
    $generatorFactory = function () use ($start, $end, $step) {
1099 2
        return iterate(
1100 2
            $start,
1101
            function ($value) use ($step, $end) {
1102 2
                $result = $value + $step;
1103
1104 2
                if ($end !== null && $result > $end) {
1105 1
                    throw new NoMoreItems;
1106
                }
1107
1108 2
                return $result;
1109
            }
1110 2
        );
1111 2
    };
1112
1113 2
    return new Collection($generatorFactory);
1114
}
1115
1116
/**
1117
 * Returns true if $input is array or Traversable object.
1118
 *
1119
 * @param mixed $input
1120
 * @return bool
1121
 */
1122
function isCollection($input)
1123
{
1124 8
    return is_array($input) || $input instanceof Traversable;
1125
}
1126
1127
/**
1128
 * Returns duplicated/cloned $input that has no relation to the original one. Used for making sure there are no side
1129
 * effect in functions.
1130
 *
1131
 * @param mixed $input
1132
 * @return mixed
1133
 */
1134
function duplicate($input)
1135
{
1136 8
    if (is_array($input)) {
1137 1
        return toArray(
1138 1
            map(
1139 1
                $input,
1140
                function ($i) {
1141 1
                    return duplicate($i);
1142
                }
1143 1
            )
1144 1
        );
1145 8
    } elseif (is_object($input)) {
1146 2
        return clone $input;
1147
    } else {
1148 8
        return $input;
1149
    }
1150
}
1151
1152
/**
1153
 * Transforms [[$key, $value], [$key2, $value2]] into [$key => $value, $key2 => $value2]. Used as a helper
1154
 *
1155
 * @param array|Traversable $collection
1156
 * @return Collection
1157
 */
1158
function dereferenceKeyValue($collection)
1159
{
1160
    $generatorFactory = function () use ($collection) {
1161 6
        foreach ($collection as $value) {
1162 6
            yield $value[0] => $value[1];
1163 6
        }
1164 7
    };
1165
1166 7
    return new Collection($generatorFactory);
1167
}
1168
1169
/**
1170
 * Realizes collection - turns lazy collection into non-lazy one by iterating over it and storing the key/values.
1171
 *
1172
 * @param array|Traversable $collection
1173
 * @return Collection
1174
 */
1175
function realize($collection)
1176
{
1177 1
    return dereferenceKeyValue(
1178 1
        toArray(
1179 1
            map(
1180 1
                $collection,
1181
                function ($value, $key) {
1182 1
                    return [$key, $value];
1183
                }
1184 1
            )
1185 1
        )
1186 1
    );
1187
}
1188
1189
/**
1190
 * Returns the second item of $collection or throws ItemNotFound if $collection is empty or has 1 item.
1191
 *
1192
 * @param array|Traversable $collection
1193
 * @return mixed
1194
 */
1195
function second($collection)
1196
{
1197 6
    return get(values($collection), 1);
1198
}
1199
1200
/**
1201
 * Combines $keys and $values into a lazy collection. The resulting collection has length equal to the size of smaller
1202
 * argument.
1203
 *
1204
 * @param array|Traversable $keys
1205
 * @param array|Traversable $values
1206
 * @return Collection
1207
 */
1208
function combine($keys, $values)
1209
{
1210
    $generatorFactory = function () use ($keys, $values) {
1211 1
        $keyCollection = new Collection($keys);
1212 1
        $valueIt = new IteratorIterator(new Collection($values));
1213 1
        $valueIt->rewind();
1214
1215 1
        foreach ($keyCollection as $key) {
1216 1
            if (!$valueIt->valid()) {
1217 1
                break;
1218
            }
1219
1220 1
            yield $key => $valueIt->current();
1221 1
            $valueIt->next();
1222 1
        }
1223 1
    };
1224
1225 1
    return new Collection($generatorFactory);
1226
}
1227
1228
/**
1229
 * Returns a lazy collection without the items associated to any of the keys from $keys.
1230
 *
1231
 * @param array|Traversable $collection
1232
 * @param array|Traversable $keys
1233
 * @return Collection
1234
 */
1235
function except($collection, $keys)
1236
{
1237 1
    $keys = toArray(values($keys));
1238
1239 1
    return reject(
1240 1
        $collection,
1241
        function ($value, $key) use ($keys) {
1242 1
            return in_array($key, $keys);
1243
        }
1244 1
    );
1245
}
1246
1247
/**
1248
 * Returns a lazy collection of items associated to any of the keys from $keys.
1249
 *
1250
 * @param array|Traversable $collection
1251
 * @param array|Traversable $keys
1252
 * @return Collection
1253
 */
1254
function only($collection, $keys)
1255
{
1256 2
    $keys = toArray(values($keys));
1257
1258 2
    return filter(
1259 2
        $collection,
1260
        function ($value, $key) use ($keys) {
1261 2
            return in_array($key, $keys, true);
1262
        }
1263 2
    );
1264
}
1265
1266
/**
1267
 * Returns a lazy collection of items that are in $collection but are not in any of the other arguments, indexed by the
1268
 * keys from the first collection. Note that the ...$collections are iterated non-lazily.
1269
 *
1270
 * @param array|Traversable $collection
1271
 * @param array|Traversable ...$collections
1272
 * @return Collection
1273
 */
1274 View Code Duplication
function diff($collection, ...$collections)
1275
{
1276 1
    $valuesToCompare = toArray(values(concat(...$collections)));
1277
    $generatorFactory = function () use ($collection, $valuesToCompare) {
1278 1
        foreach ($collection as $key => $value) {
1279 1
            if (!in_array($value, $valuesToCompare)) {
1280 1
                yield $key => $value;
1281 1
            }
1282 1
        }
1283 1
    };
1284
1285 1
    return new Collection($generatorFactory);
1286
}
1287
1288
/**
1289
 * Returns a lazy collection of items that are in $collection and all the other arguments, indexed by the keys from the
1290
 * first collection. Note that the ...$collections are iterated non-lazily.
1291
 *
1292
 * @param array|Traversable $collection
1293
 * @param array|Traversable ...$collections
1294
 * @return Collection
1295
 */
1296 View Code Duplication
function intersect($collection, ...$collections)
1297
{
1298 1
    $valuesToCompare = toArray(values(concat(...$collections)));
1299
    $generatorFactory = function () use ($collection, $valuesToCompare) {
1300 1
        foreach ($collection as $key => $value) {
1301 1
            if (in_array($value, $valuesToCompare)) {
1302 1
                yield $key => $value;
1303 1
            }
1304 1
        }
1305 1
    };
1306
1307 1
    return new Collection($generatorFactory);
1308
}
1309
1310
/**
1311
 * Returns a lazy collection where keys and values are flipped.
1312
 *
1313
 * @param array|Traversable $collection
1314
 * @return Collection
1315
 */
1316
function flip($collection)
1317
{
1318
    $generatorFactory = function () use ($collection) {
1319 1
        foreach ($collection as $key => $value) {
1320 1
            yield $value => $key;
1321 1
        }
1322 1
    };
1323
1324 1
    return new Collection($generatorFactory);
1325
}
1326
1327
/**
1328
 * Checks for the existence of an item with key $key in $collection.
1329
 *
1330
 * @param array|Traversable $collection
1331
 * @param mixed $key
1332
 * @return bool
1333
 */
1334
function has($collection, $key)
1335
{
1336
    try {
1337 2
        get($collection, $key);
1338 2
        return true;
1339 2
    } catch (ItemNotFound $e) {
1340 2
        return false;
1341
    }
1342
}
1343
1344
/**
1345
 * Returns a lazy collection of non-lazy collections of items from nth position from each passed collection. Stops when
1346
 * any of the collections don't have an item at the nth position.
1347
 *
1348
 * @param array|Traversable ...$collections
1349
 * @return Collection
1350
 */
1351
function zip(...$collections)
1352
{
1353
    /* @var Iterator[] $iterators */
1354 1
    $iterators = array_map(
1355
        function ($collection) {
1356 1
            $it = new IteratorIterator(new Collection($collection));
1357 1
            $it->rewind();
1358 1
            return $it;
1359 1
        },
1360
        $collections
1361 1
    );
1362
1363
    $generatorFactory = function () use ($iterators) {
1364 1
        while (true) {
1365 1
            $isMissingItems = false;
1366 1
            $zippedItem = new Collection([]);
1367
1368 1
            foreach ($iterators as $it) {
1369 1
                if (!$it->valid()) {
1370 1
                    $isMissingItems = true;
1371 1
                    break;
1372
                }
1373
1374 1
                $zippedItem = append($zippedItem, $it->current(), $it->key());
1375 1
                $it->next();
1376 1
            }
1377
1378 1
            if (!$isMissingItems) {
1379 1
                yield $zippedItem;
1380 1
            } else {
1381 1
                break;
1382
            }
1383 1
        }
1384 1
    };
1385
1386 1
    return new Collection($generatorFactory);
1387
}
1388
1389
/**
1390
 * Transpose each item in a collection, interchanging the row and column indexes.
1391
 * Can only transpose collections of collections. Otherwise an InvalidArgument is raised.
1392
 *
1393
 * @param Collection[] $collection
1394
 * @return Collection
1395
 */
1396
function transpose($collection)
1397
{
1398
    if (some($collection, function ($value) {
1399 3
        return !($value instanceof Collection);
1400 3
    })) {
1401 1
        throw new InvalidArgument('Can only transpose collections of collections.');
1402
    }
1403
1404 2
    return Collection::from(
1405 2
        array_map(
1406
            function (...$items) {
1407 2
                return new Collection($items);
1408 2
            },
1409 2
            ...toArray(
1410 2
                map(
1411 2
                    $collection,
1412
                    'DusanKasan\Knapsack\toArray'
1413 2
                )
1414 2
            )
1415 2
        )
1416 2
    );
1417
}
1418
1419
/**
1420
 * Returns a lazy collection of data extracted from $collection items by dot separated key path. Supports the *
1421
 * wildcard. If a key contains \ or * it must be escaped using \ character.
1422
 *
1423
 * @param array|Traversable $collection
1424
 * @param mixed $keyPath
1425
 * @return Collection
1426
 */
1427
function extract($collection, $keyPath)
1428
{
1429 1
    preg_match_all('/(.*[^\\\])(?:\.|$)/U', $keyPath, $matches);
1430 1
    $pathParts = $matches[1];
1431
1432
    $extractor = function ($coll) use ($pathParts) {
1433 1
        foreach ($pathParts as $pathPart) {
1434 1
            $coll = flatten(filter($coll, '\DusanKasan\Knapsack\isCollection'), 1);
1435
1436 1
            if ($pathPart != '*') {
1437 1
                $pathPart = str_replace(['\.', '\*'], ['.', '*'], $pathPart);
1438 1
                $coll = values(only($coll, [$pathPart]));
1439 1
            }
1440 1
        }
1441
1442 1
        return $coll;
1443 1
    };
1444
1445
    $generatorFactory = function () use ($collection, $extractor) {
1446 1
        foreach ($collection as $item) {
1447 1
            foreach ($extractor([$item]) as $extracted) {
1448 1
                yield $extracted;
1449 1
            }
1450 1
        }
1451 1
    };
1452
1453 1
    return new Collection($generatorFactory);
1454
}
1455
1456
/**
1457
 * Checks whether $collection has exactly $size items.
1458
 *
1459
 * @param array|Traversable $collection
1460
 * @param int $size
1461
 * @return bool
1462
 */
1463 View Code Duplication
function sizeIs($collection, $size)
1464
{
1465 1
    $itemsTempCount = 0;
1466
1467 1
    foreach ($collection as $key => $value) {
1468 1
        $itemsTempCount++;
1469
1470 1
        if ($itemsTempCount > $size) {
1471 1
            return false;
1472
        }
1473 1
    }
1474
1475 1
    return $itemsTempCount == $size;
1476
}
1477
1478
/**
1479
 * Checks whether $collection has less than $size items.
1480
 *
1481
 * @param array|Traversable $collection
1482
 * @param int $size
1483
 * @return bool
1484
 */
1485 View Code Duplication
function sizeIsLessThan($collection, $size)
1486
{
1487 1
    $itemsTempCount = 0;
1488
1489 1
    foreach ($collection as $key => $value) {
1490 1
        $itemsTempCount++;
1491
1492 1
        if ($itemsTempCount > $size) {
1493 1
            return false;
1494
        }
1495 1
    }
1496
1497 1
    return $itemsTempCount < $size;
1498
}
1499
1500
/**
1501
 * Checks whether $collection has more than $size items.
1502
 *
1503
 * @param array|Traversable $collection
1504
 * @param int $size
1505
 * @return bool
1506
 */
1507 View Code Duplication
function sizeIsGreaterThan($collection, $size)
1508
{
1509 1
    $itemsTempCount = 0;
1510
1511 1
    foreach ($collection as $key => $value) {
1512 1
        $itemsTempCount++;
1513
1514 1
        if ($itemsTempCount > $size) {
1515 1
            return true;
1516
        }
1517 1
    }
1518
1519 1
    return $itemsTempCount > $size;
1520
}
1521
1522
/**
1523
 * Checks whether $collection has between $fromSize to $toSize items. $toSize can be
1524
 * smaller than $fromSize.
1525
 *
1526
 * @param array|Traversable $collection
1527
 * @param int $fromSize
1528
 * @param int $toSize
1529
 * @return bool
1530
 */
1531
function sizeIsBetween($collection, $fromSize, $toSize)
1532
{
1533 1
    if ($fromSize > $toSize) {
1534 1
        $tmp = $toSize;
1535 1
        $toSize = $fromSize;
1536 1
        $fromSize = $tmp;
1537 1
    }
1538
1539 1
    $itemsTempCount = 0;
1540 1
    foreach ($collection as $key => $value) {
1541 1
        $itemsTempCount++;
1542
1543 1
        if ($itemsTempCount > $toSize) {
1544 1
            return false;
1545
        }
1546 1
    }
1547
1548 1
    return $fromSize < $itemsTempCount && $itemsTempCount < $toSize;
1549
}
1550
1551
/**
1552
 * Returns a sum of all values in the $collection.
1553
 *
1554
 * @param array|Traversable $collection
1555
 * @return int|float
1556
 */
1557
function sum($collection)
1558
{
1559 1
    $result = 0;
1560
1561 1
    foreach ($collection as $value) {
1562 1
        $result += $value;
1563 1
    }
1564
1565 1
    return $result;
1566
}
1567
1568
/**
1569
 * Returns average of values from $collection.
1570
 *
1571
 * @param array|Traversable $collection
1572
 * @return int|float
1573
 */
1574
function average($collection)
1575
{
1576 2
    $sum = 0;
1577 2
    $count = 0;
1578
1579 2
    foreach ($collection as $value) {
1580 1
        $sum += $value;
1581 1
        $count++;
1582 2
    }
1583
1584 2
    return $count ? $sum/$count : 0;
1585
}
1586
1587
/**
1588
 * Returns maximal value from $collection.
1589
 *
1590
 * @param array|Traversable $collection
1591
 * @return mixed
1592
 */
1593
function max($collection)
1594
{
1595 2
    $result = null;
1596
1597 2
    foreach ($collection as $value) {
1598 1
        $result = $value > $result ? $value : $result;
1599 2
    }
1600
1601 2
    return $result;
1602
}
1603
1604
/**
1605
 * Returns minimal value from $collection.
1606
 *
1607
 * @param array|Traversable $collection
1608
 * @return mixed
1609
 */
1610
function min($collection)
1611
{
1612 2
    $result = null;
1613 2
    $hasItem = false;
1614
1615 2
    foreach ($collection as $value) {
1616 1
        if (!$hasItem) {
1617 1
            $hasItem = true;
1618 1
            $result = $value;
1619 1
        }
1620
1621 1
        $result = $value < $result ? $value : $result;
1622 2
    }
1623
1624 2
    return $result;
1625
}
1626
1627
/**
1628
 * Returns a string by concatenating the $collection values into a string.
1629
 *
1630
 * @param array|Traversable $collection
1631
 * @return string
1632
 */
1633
function toString($collection)
1634
{
1635 1
    $result = '';
1636
1637 1
    foreach ($collection as $value) {
1638 1
        $result .= (string) $value;
1639 1
    }
1640
1641 1
    return $result;
1642
}
1643
1644
1645
/**
1646
 * Returns a lazy collection with items from $collection, but items with keys  that are found in keys of $replacementMap
1647
 * are replaced by their values.
1648
 *
1649
 * @param array|Traversable $collection
1650
 * @param array|Traversable $replacementMap
1651
 * @return Collection
1652
 */
1653 View Code Duplication
function replaceByKeys($collection, $replacementMap)
1654
{
1655
    $generatorFactory = function () use ($collection, $replacementMap) {
1656 1
        foreach ($collection as $key => $value) {
1657 1
            $newValue = getOrDefault($replacementMap, $key, $value);
1658 1
            yield $key => $newValue;
1659 1
        }
1660 1
    };
1661
1662 1
    return new Collection($generatorFactory);
1663
}
1664
1665
/**
1666
 * Dumps a variable into scalar or array (recursively).
1667
 *
1668
 * - scalars are returned as they are,
1669
 * - array of class name => properties (name => value and only properties accessible for this class)
1670
 *   is returned for objects,
1671
 * - arrays or Traversables are returned as arrays,
1672
 * - for anything else result of calling gettype($input) is returned
1673
 *
1674
 * If specified, $maxItemsPerCollection will only leave specified number of items in collection,
1675
 * appending a new element at end '>>>' if original collection was longer.
1676
 *
1677
 * If specified, $maxDepth will only leave specified n levels of nesting, replacing elements
1678
 * with '^^^' once the maximum nesting level was reached.
1679
 *
1680
 * If a collection with duplicate keys is encountered, the duplicate keys (except the first one)
1681
 * will be change into a format originalKey//duplicateCounter where duplicateCounter starts from
1682
 * 1 at the first duplicate. So [0 => 1, 0 => 2] will become [0 => 1, '0//1' => 2]
1683
 *
1684
 * @param mixed $input
1685
 * @param int|null $maxItemsPerCollection
1686
 * @param int|null $maxDepth
1687
 * @return array|mixed
1688
 */
1689
function dump($input, $maxItemsPerCollection = null, $maxDepth = null)
1690
{
1691 2
    if (is_scalar($input)) {
1692 2
        return $input;
1693
    }
1694
1695 2
    if (is_array($input) || $input instanceof Traversable) {
1696 2
        if ($maxDepth === 0) {
1697 1
            return '^^^';
1698
        }
1699
1700 2
        $normalizedProperties = [];
1701 2
        foreach ($input as $key => $value) {
1702 2
            if ($maxItemsPerCollection !== null && count($normalizedProperties) >= $maxItemsPerCollection) {
1703 2
                $normalizedProperties[] = '>>>';
1704 2
                break;
1705
            }
1706
1707 2
            for ($affix = 0; true; $affix++) {
1708 2
                $betterKey = $affix ? "$key//$affix" : $key;
1709 2
                if (!array_key_exists($betterKey, $normalizedProperties)) {
1710 2
                    $normalizedProperties[$betterKey] = dump(
1711 2
                        $value,
1712 2
                        $maxItemsPerCollection,
1713 2
                        $maxDepth>0 ? $maxDepth-1 : null
1714 2
                    );
1715
1716 2
                    break;
1717
                }
1718 1
            }
1719 2
        }
1720
1721 2
        return $normalizedProperties;
1722
    }
1723
1724 1
    if (is_object($input)) {
1725 1
        if ($maxDepth === 0) {
1726
            return '^^^';
1727
        }
1728
1729 1
        $reflection = new \ReflectionObject($input);
1730 1
        $normalizedProperties = [];
1731 1
        foreach ($reflection->getProperties() as $property) {
1732 1
            $property->setAccessible(true);
1733 1
            $normalizedProperties[$property->getName()] = $property->getValue($input);
1734 1
        }
1735 1
        return [get_class($input) => dump($normalizedProperties, null, $maxDepth>0 ? $maxDepth-1 : null)];
1736
    }
1737
1738
    return gettype($input);
1739
}
1740
1741
/**
1742
 * Calls dump on $input and then prints it using the var_export. Returns $input.
1743
 *
1744
 * @param mixed $input
1745
 * @param int|null $maxItemsPerCollection
1746
 * @param int|null $maxDepth
1747
 * @return mixed
1748
 */
1749
function printDump($input, $maxItemsPerCollection = null, $maxDepth = null)
1750
{
1751 1
    var_export(dump($input, $maxItemsPerCollection, $maxDepth));
1752 1
    return $input;
1753
}
1754