1<?php
2
3namespace Illuminate\Support;
4
5use ArrayAccess;
6use ArrayIterator;
7use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString;
8use Illuminate\Support\Traits\EnumeratesValues;
9use Illuminate\Support\Traits\Macroable;
10use stdClass;
11
12class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerable
13{
14    use EnumeratesValues, Macroable;
15
16    /**
17     * The items contained in the collection.
18     *
19     * @var array
20     */
21    protected $items = [];
22
23    /**
24     * Create a new collection.
25     *
26     * @param  mixed  $items
27     * @return void
28     */
29    public function __construct($items = [])
30    {
31        $this->items = $this->getArrayableItems($items);
32    }
33
34    /**
35     * Create a collection with the given range.
36     *
37     * @param  int  $from
38     * @param  int  $to
39     * @return static
40     */
41    public static function range($from, $to)
42    {
43        return new static(range($from, $to));
44    }
45
46    /**
47     * Get all of the items in the collection.
48     *
49     * @return array
50     */
51    public function all()
52    {
53        return $this->items;
54    }
55
56    /**
57     * Get a lazy collection for the items in this collection.
58     *
59     * @return \Illuminate\Support\LazyCollection
60     */
61    public function lazy()
62    {
63        return new LazyCollection($this->items);
64    }
65
66    /**
67     * Get the average value of a given key.
68     *
69     * @param  callable|string|null  $callback
70     * @return mixed
71     */
72    public function avg($callback = null)
73    {
74        $callback = $this->valueRetriever($callback);
75
76        $items = $this->map(function ($value) use ($callback) {
77            return $callback($value);
78        })->filter(function ($value) {
79            return ! is_null($value);
80        });
81
82        if ($count = $items->count()) {
83            return $items->sum() / $count;
84        }
85    }
86
87    /**
88     * Get the median of a given key.
89     *
90     * @param  string|array|null  $key
91     * @return mixed
92     */
93    public function median($key = null)
94    {
95        $values = (isset($key) ? $this->pluck($key) : $this)
96            ->filter(function ($item) {
97                return ! is_null($item);
98            })->sort()->values();
99
100        $count = $values->count();
101
102        if ($count === 0) {
103            return;
104        }
105
106        $middle = (int) ($count / 2);
107
108        if ($count % 2) {
109            return $values->get($middle);
110        }
111
112        return (new static([
113            $values->get($middle - 1), $values->get($middle),
114        ]))->average();
115    }
116
117    /**
118     * Get the mode of a given key.
119     *
120     * @param  string|array|null  $key
121     * @return array|null
122     */
123    public function mode($key = null)
124    {
125        if ($this->count() === 0) {
126            return;
127        }
128
129        $collection = isset($key) ? $this->pluck($key) : $this;
130
131        $counts = new static;
132
133        $collection->each(function ($value) use ($counts) {
134            $counts[$value] = isset($counts[$value]) ? $counts[$value] + 1 : 1;
135        });
136
137        $sorted = $counts->sort();
138
139        $highestValue = $sorted->last();
140
141        return $sorted->filter(function ($value) use ($highestValue) {
142            return $value == $highestValue;
143        })->sort()->keys()->all();
144    }
145
146    /**
147     * Collapse the collection of items into a single array.
148     *
149     * @return static
150     */
151    public function collapse()
152    {
153        return new static(Arr::collapse($this->items));
154    }
155
156    /**
157     * Determine if an item exists in the collection.
158     *
159     * @param  mixed  $key
160     * @param  mixed  $operator
161     * @param  mixed  $value
162     * @return bool
163     */
164    public function contains($key, $operator = null, $value = null)
165    {
166        if (func_num_args() === 1) {
167            if ($this->useAsCallable($key)) {
168                $placeholder = new stdClass;
169
170                return $this->first($key, $placeholder) !== $placeholder;
171            }
172
173            return in_array($key, $this->items);
174        }
175
176        return $this->contains($this->operatorForWhere(...func_get_args()));
177    }
178
179    /**
180     * Cross join with the given lists, returning all possible permutations.
181     *
182     * @param  mixed  ...$lists
183     * @return static
184     */
185    public function crossJoin(...$lists)
186    {
187        return new static(Arr::crossJoin(
188            $this->items, ...array_map([$this, 'getArrayableItems'], $lists)
189        ));
190    }
191
192    /**
193     * Get the items in the collection that are not present in the given items.
194     *
195     * @param  mixed  $items
196     * @return static
197     */
198    public function diff($items)
199    {
200        return new static(array_diff($this->items, $this->getArrayableItems($items)));
201    }
202
203    /**
204     * Get the items in the collection that are not present in the given items, using the callback.
205     *
206     * @param  mixed  $items
207     * @param  callable  $callback
208     * @return static
209     */
210    public function diffUsing($items, callable $callback)
211    {
212        return new static(array_udiff($this->items, $this->getArrayableItems($items), $callback));
213    }
214
215    /**
216     * Get the items in the collection whose keys and values are not present in the given items.
217     *
218     * @param  mixed  $items
219     * @return static
220     */
221    public function diffAssoc($items)
222    {
223        return new static(array_diff_assoc($this->items, $this->getArrayableItems($items)));
224    }
225
226    /**
227     * Get the items in the collection whose keys and values are not present in the given items, using the callback.
228     *
229     * @param  mixed  $items
230     * @param  callable  $callback
231     * @return static
232     */
233    public function diffAssocUsing($items, callable $callback)
234    {
235        return new static(array_diff_uassoc($this->items, $this->getArrayableItems($items), $callback));
236    }
237
238    /**
239     * Get the items in the collection whose keys are not present in the given items.
240     *
241     * @param  mixed  $items
242     * @return static
243     */
244    public function diffKeys($items)
245    {
246        return new static(array_diff_key($this->items, $this->getArrayableItems($items)));
247    }
248
249    /**
250     * Get the items in the collection whose keys are not present in the given items, using the callback.
251     *
252     * @param  mixed  $items
253     * @param  callable  $callback
254     * @return static
255     */
256    public function diffKeysUsing($items, callable $callback)
257    {
258        return new static(array_diff_ukey($this->items, $this->getArrayableItems($items), $callback));
259    }
260
261    /**
262     * Retrieve duplicate items from the collection.
263     *
264     * @param  callable|string|null  $callback
265     * @param  bool  $strict
266     * @return static
267     */
268    public function duplicates($callback = null, $strict = false)
269    {
270        $items = $this->map($this->valueRetriever($callback));
271
272        $uniqueItems = $items->unique(null, $strict);
273
274        $compare = $this->duplicateComparator($strict);
275
276        $duplicates = new static;
277
278        foreach ($items as $key => $value) {
279            if ($uniqueItems->isNotEmpty() && $compare($value, $uniqueItems->first())) {
280                $uniqueItems->shift();
281            } else {
282                $duplicates[$key] = $value;
283            }
284        }
285
286        return $duplicates;
287    }
288
289    /**
290     * Retrieve duplicate items from the collection using strict comparison.
291     *
292     * @param  callable|string|null  $callback
293     * @return static
294     */
295    public function duplicatesStrict($callback = null)
296    {
297        return $this->duplicates($callback, true);
298    }
299
300    /**
301     * Get the comparison function to detect duplicates.
302     *
303     * @param  bool  $strict
304     * @return \Closure
305     */
306    protected function duplicateComparator($strict)
307    {
308        if ($strict) {
309            return function ($a, $b) {
310                return $a === $b;
311            };
312        }
313
314        return function ($a, $b) {
315            return $a == $b;
316        };
317    }
318
319    /**
320     * Get all items except for those with the specified keys.
321     *
322     * @param  \Illuminate\Support\Collection|mixed  $keys
323     * @return static
324     */
325    public function except($keys)
326    {
327        if ($keys instanceof Enumerable) {
328            $keys = $keys->all();
329        } elseif (! is_array($keys)) {
330            $keys = func_get_args();
331        }
332
333        return new static(Arr::except($this->items, $keys));
334    }
335
336    /**
337     * Run a filter over each of the items.
338     *
339     * @param  callable|null  $callback
340     * @return static
341     */
342    public function filter(callable $callback = null)
343    {
344        if ($callback) {
345            return new static(Arr::where($this->items, $callback));
346        }
347
348        return new static(array_filter($this->items));
349    }
350
351    /**
352     * Get the first item from the collection passing the given truth test.
353     *
354     * @param  callable|null  $callback
355     * @param  mixed  $default
356     * @return mixed
357     */
358    public function first(callable $callback = null, $default = null)
359    {
360        return Arr::first($this->items, $callback, $default);
361    }
362
363    /**
364     * Get a flattened array of the items in the collection.
365     *
366     * @param  int  $depth
367     * @return static
368     */
369    public function flatten($depth = INF)
370    {
371        return new static(Arr::flatten($this->items, $depth));
372    }
373
374    /**
375     * Flip the items in the collection.
376     *
377     * @return static
378     */
379    public function flip()
380    {
381        return new static(array_flip($this->items));
382    }
383
384    /**
385     * Remove an item from the collection by key.
386     *
387     * @param  string|array  $keys
388     * @return $this
389     */
390    public function forget($keys)
391    {
392        foreach ((array) $keys as $key) {
393            $this->offsetUnset($key);
394        }
395
396        return $this;
397    }
398
399    /**
400     * Get an item from the collection by key.
401     *
402     * @param  mixed  $key
403     * @param  mixed  $default
404     * @return mixed
405     */
406    public function get($key, $default = null)
407    {
408        if (array_key_exists($key, $this->items)) {
409            return $this->items[$key];
410        }
411
412        return value($default);
413    }
414
415    /**
416     * Group an associative array by a field or using a callback.
417     *
418     * @param  array|callable|string  $groupBy
419     * @param  bool  $preserveKeys
420     * @return static
421     */
422    public function groupBy($groupBy, $preserveKeys = false)
423    {
424        if (! $this->useAsCallable($groupBy) && is_array($groupBy)) {
425            $nextGroups = $groupBy;
426
427            $groupBy = array_shift($nextGroups);
428        }
429
430        $groupBy = $this->valueRetriever($groupBy);
431
432        $results = [];
433
434        foreach ($this->items as $key => $value) {
435            $groupKeys = $groupBy($value, $key);
436
437            if (! is_array($groupKeys)) {
438                $groupKeys = [$groupKeys];
439            }
440
441            foreach ($groupKeys as $groupKey) {
442                $groupKey = is_bool($groupKey) ? (int) $groupKey : $groupKey;
443
444                if (! array_key_exists($groupKey, $results)) {
445                    $results[$groupKey] = new static;
446                }
447
448                $results[$groupKey]->offsetSet($preserveKeys ? $key : null, $value);
449            }
450        }
451
452        $result = new static($results);
453
454        if (! empty($nextGroups)) {
455            return $result->map->groupBy($nextGroups, $preserveKeys);
456        }
457
458        return $result;
459    }
460
461    /**
462     * Key an associative array by a field or using a callback.
463     *
464     * @param  callable|string  $keyBy
465     * @return static
466     */
467    public function keyBy($keyBy)
468    {
469        $keyBy = $this->valueRetriever($keyBy);
470
471        $results = [];
472
473        foreach ($this->items as $key => $item) {
474            $resolvedKey = $keyBy($item, $key);
475
476            if (is_object($resolvedKey)) {
477                $resolvedKey = (string) $resolvedKey;
478            }
479
480            $results[$resolvedKey] = $item;
481        }
482
483        return new static($results);
484    }
485
486    /**
487     * Determine if an item exists in the collection by key.
488     *
489     * @param  mixed  $key
490     * @return bool
491     */
492    public function has($key)
493    {
494        $keys = is_array($key) ? $key : func_get_args();
495
496        foreach ($keys as $value) {
497            if (! array_key_exists($value, $this->items)) {
498                return false;
499            }
500        }
501
502        return true;
503    }
504
505    /**
506     * Determine if any of the keys exist in the collection.
507     *
508     * @param  mixed  $key
509     * @return bool
510     */
511    public function hasAny($key)
512    {
513        if ($this->isEmpty()) {
514            return false;
515        }
516
517        $keys = is_array($key) ? $key : func_get_args();
518
519        foreach ($keys as $value) {
520            if ($this->has($value)) {
521                return true;
522            }
523        }
524
525        return false;
526    }
527
528    /**
529     * Concatenate values of a given key as a string.
530     *
531     * @param  string  $value
532     * @param  string|null  $glue
533     * @return string
534     */
535    public function implode($value, $glue = null)
536    {
537        $first = $this->first();
538
539        if (is_array($first) || (is_object($first) && ! $first instanceof Stringable)) {
540            return implode($glue ?? '', $this->pluck($value)->all());
541        }
542
543        return implode($value ?? '', $this->items);
544    }
545
546    /**
547     * Intersect the collection with the given items.
548     *
549     * @param  mixed  $items
550     * @return static
551     */
552    public function intersect($items)
553    {
554        return new static(array_intersect($this->items, $this->getArrayableItems($items)));
555    }
556
557    /**
558     * Intersect the collection with the given items by key.
559     *
560     * @param  mixed  $items
561     * @return static
562     */
563    public function intersectByKeys($items)
564    {
565        return new static(array_intersect_key(
566            $this->items, $this->getArrayableItems($items)
567        ));
568    }
569
570    /**
571     * Determine if the collection is empty or not.
572     *
573     * @return bool
574     */
575    public function isEmpty()
576    {
577        return empty($this->items);
578    }
579
580    /**
581     * Determine if the collection contains a single item.
582     *
583     * @return bool
584     */
585    public function containsOneItem()
586    {
587        return $this->count() === 1;
588    }
589
590    /**
591     * Join all items from the collection using a string. The final items can use a separate glue string.
592     *
593     * @param  string  $glue
594     * @param  string  $finalGlue
595     * @return string
596     */
597    public function join($glue, $finalGlue = '')
598    {
599        if ($finalGlue === '') {
600            return $this->implode($glue);
601        }
602
603        $count = $this->count();
604
605        if ($count === 0) {
606            return '';
607        }
608
609        if ($count === 1) {
610            return $this->last();
611        }
612
613        $collection = new static($this->items);
614
615        $finalItem = $collection->pop();
616
617        return $collection->implode($glue).$finalGlue.$finalItem;
618    }
619
620    /**
621     * Get the keys of the collection items.
622     *
623     * @return static
624     */
625    public function keys()
626    {
627        return new static(array_keys($this->items));
628    }
629
630    /**
631     * Get the last item from the collection.
632     *
633     * @param  callable|null  $callback
634     * @param  mixed  $default
635     * @return mixed
636     */
637    public function last(callable $callback = null, $default = null)
638    {
639        return Arr::last($this->items, $callback, $default);
640    }
641
642    /**
643     * Get the values of a given key.
644     *
645     * @param  string|array|int|null  $value
646     * @param  string|null  $key
647     * @return static
648     */
649    public function pluck($value, $key = null)
650    {
651        return new static(Arr::pluck($this->items, $value, $key));
652    }
653
654    /**
655     * Run a map over each of the items.
656     *
657     * @param  callable  $callback
658     * @return static
659     */
660    public function map(callable $callback)
661    {
662        $keys = array_keys($this->items);
663
664        $items = array_map($callback, $this->items, $keys);
665
666        return new static(array_combine($keys, $items));
667    }
668
669    /**
670     * Run a dictionary map over the items.
671     *
672     * The callback should return an associative array with a single key/value pair.
673     *
674     * @param  callable  $callback
675     * @return static
676     */
677    public function mapToDictionary(callable $callback)
678    {
679        $dictionary = [];
680
681        foreach ($this->items as $key => $item) {
682            $pair = $callback($item, $key);
683
684            $key = key($pair);
685
686            $value = reset($pair);
687
688            if (! isset($dictionary[$key])) {
689                $dictionary[$key] = [];
690            }
691
692            $dictionary[$key][] = $value;
693        }
694
695        return new static($dictionary);
696    }
697
698    /**
699     * Run an associative map over each of the items.
700     *
701     * The callback should return an associative array with a single key/value pair.
702     *
703     * @param  callable  $callback
704     * @return static
705     */
706    public function mapWithKeys(callable $callback)
707    {
708        $result = [];
709
710        foreach ($this->items as $key => $value) {
711            $assoc = $callback($value, $key);
712
713            foreach ($assoc as $mapKey => $mapValue) {
714                $result[$mapKey] = $mapValue;
715            }
716        }
717
718        return new static($result);
719    }
720
721    /**
722     * Merge the collection with the given items.
723     *
724     * @param  mixed  $items
725     * @return static
726     */
727    public function merge($items)
728    {
729        return new static(array_merge($this->items, $this->getArrayableItems($items)));
730    }
731
732    /**
733     * Recursively merge the collection with the given items.
734     *
735     * @param  mixed  $items
736     * @return static
737     */
738    public function mergeRecursive($items)
739    {
740        return new static(array_merge_recursive($this->items, $this->getArrayableItems($items)));
741    }
742
743    /**
744     * Create a collection by using this collection for keys and another for its values.
745     *
746     * @param  mixed  $values
747     * @return static
748     */
749    public function combine($values)
750    {
751        return new static(array_combine($this->all(), $this->getArrayableItems($values)));
752    }
753
754    /**
755     * Union the collection with the given items.
756     *
757     * @param  mixed  $items
758     * @return static
759     */
760    public function union($items)
761    {
762        return new static($this->items + $this->getArrayableItems($items));
763    }
764
765    /**
766     * Create a new collection consisting of every n-th element.
767     *
768     * @param  int  $step
769     * @param  int  $offset
770     * @return static
771     */
772    public function nth($step, $offset = 0)
773    {
774        $new = [];
775
776        $position = 0;
777
778        foreach ($this->items as $item) {
779            if ($position % $step === $offset) {
780                $new[] = $item;
781            }
782
783            $position++;
784        }
785
786        return new static($new);
787    }
788
789    /**
790     * Get the items with the specified keys.
791     *
792     * @param  mixed  $keys
793     * @return static
794     */
795    public function only($keys)
796    {
797        if (is_null($keys)) {
798            return new static($this->items);
799        }
800
801        if ($keys instanceof Enumerable) {
802            $keys = $keys->all();
803        }
804
805        $keys = is_array($keys) ? $keys : func_get_args();
806
807        return new static(Arr::only($this->items, $keys));
808    }
809
810    /**
811     * Get and remove the last N items from the collection.
812     *
813     * @param  int  $count
814     * @return mixed
815     */
816    public function pop($count = 1)
817    {
818        if ($count === 1) {
819            return array_pop($this->items);
820        }
821
822        if ($this->isEmpty()) {
823            return new static;
824        }
825
826        $results = [];
827
828        $collectionCount = $this->count();
829
830        foreach (range(1, min($count, $collectionCount)) as $item) {
831            array_push($results, array_pop($this->items));
832        }
833
834        return new static($results);
835    }
836
837    /**
838     * Push an item onto the beginning of the collection.
839     *
840     * @param  mixed  $value
841     * @param  mixed  $key
842     * @return $this
843     */
844    public function prepend($value, $key = null)
845    {
846        $this->items = Arr::prepend($this->items, ...func_get_args());
847
848        return $this;
849    }
850
851    /**
852     * Push one or more items onto the end of the collection.
853     *
854     * @param  mixed  $values
855     * @return $this
856     */
857    public function push(...$values)
858    {
859        foreach ($values as $value) {
860            $this->items[] = $value;
861        }
862
863        return $this;
864    }
865
866    /**
867     * Push all of the given items onto the collection.
868     *
869     * @param  iterable  $source
870     * @return static
871     */
872    public function concat($source)
873    {
874        $result = new static($this);
875
876        foreach ($source as $item) {
877            $result->push($item);
878        }
879
880        return $result;
881    }
882
883    /**
884     * Get and remove an item from the collection.
885     *
886     * @param  mixed  $key
887     * @param  mixed  $default
888     * @return mixed
889     */
890    public function pull($key, $default = null)
891    {
892        return Arr::pull($this->items, $key, $default);
893    }
894
895    /**
896     * Put an item in the collection by key.
897     *
898     * @param  mixed  $key
899     * @param  mixed  $value
900     * @return $this
901     */
902    public function put($key, $value)
903    {
904        $this->offsetSet($key, $value);
905
906        return $this;
907    }
908
909    /**
910     * Get one or a specified number of items randomly from the collection.
911     *
912     * @param  int|null  $number
913     * @return static|mixed
914     *
915     * @throws \InvalidArgumentException
916     */
917    public function random($number = null)
918    {
919        if (is_null($number)) {
920            return Arr::random($this->items);
921        }
922
923        return new static(Arr::random($this->items, $number));
924    }
925
926    /**
927     * Replace the collection items with the given items.
928     *
929     * @param  mixed  $items
930     * @return static
931     */
932    public function replace($items)
933    {
934        return new static(array_replace($this->items, $this->getArrayableItems($items)));
935    }
936
937    /**
938     * Recursively replace the collection items with the given items.
939     *
940     * @param  mixed  $items
941     * @return static
942     */
943    public function replaceRecursive($items)
944    {
945        return new static(array_replace_recursive($this->items, $this->getArrayableItems($items)));
946    }
947
948    /**
949     * Reverse items order.
950     *
951     * @return static
952     */
953    public function reverse()
954    {
955        return new static(array_reverse($this->items, true));
956    }
957
958    /**
959     * Search the collection for a given value and return the corresponding key if successful.
960     *
961     * @param  mixed  $value
962     * @param  bool  $strict
963     * @return mixed
964     */
965    public function search($value, $strict = false)
966    {
967        if (! $this->useAsCallable($value)) {
968            return array_search($value, $this->items, $strict);
969        }
970
971        foreach ($this->items as $key => $item) {
972            if ($value($item, $key)) {
973                return $key;
974            }
975        }
976
977        return false;
978    }
979
980    /**
981     * Get and remove the first N items from the collection.
982     *
983     * @param  int  $count
984     * @return mixed
985     */
986    public function shift($count = 1)
987    {
988        if ($count === 1) {
989            return array_shift($this->items);
990        }
991
992        if ($this->isEmpty()) {
993            return new static;
994        }
995
996        $results = [];
997
998        $collectionCount = $this->count();
999
1000        foreach (range(1, min($count, $collectionCount)) as $item) {
1001            array_push($results, array_shift($this->items));
1002        }
1003
1004        return new static($results);
1005    }
1006
1007    /**
1008     * Shuffle the items in the collection.
1009     *
1010     * @param  int|null  $seed
1011     * @return static
1012     */
1013    public function shuffle($seed = null)
1014    {
1015        return new static(Arr::shuffle($this->items, $seed));
1016    }
1017
1018    /**
1019     * Create chunks representing a "sliding window" view of the items in the collection.
1020     *
1021     * @param  int  $size
1022     * @param  int  $step
1023     * @return static
1024     */
1025    public function sliding($size = 2, $step = 1)
1026    {
1027        $chunks = floor(($this->count() - $size) / $step) + 1;
1028
1029        return static::times($chunks, function ($number) use ($size, $step) {
1030            return $this->slice(($number - 1) * $step, $size);
1031        });
1032    }
1033
1034    /**
1035     * Skip the first {$count} items.
1036     *
1037     * @param  int  $count
1038     * @return static
1039     */
1040    public function skip($count)
1041    {
1042        return $this->slice($count);
1043    }
1044
1045    /**
1046     * Skip items in the collection until the given condition is met.
1047     *
1048     * @param  mixed  $value
1049     * @return static
1050     */
1051    public function skipUntil($value)
1052    {
1053        return new static($this->lazy()->skipUntil($value)->all());
1054    }
1055
1056    /**
1057     * Skip items in the collection while the given condition is met.
1058     *
1059     * @param  mixed  $value
1060     * @return static
1061     */
1062    public function skipWhile($value)
1063    {
1064        return new static($this->lazy()->skipWhile($value)->all());
1065    }
1066
1067    /**
1068     * Slice the underlying collection array.
1069     *
1070     * @param  int  $offset
1071     * @param  int|null  $length
1072     * @return static
1073     */
1074    public function slice($offset, $length = null)
1075    {
1076        return new static(array_slice($this->items, $offset, $length, true));
1077    }
1078
1079    /**
1080     * Split a collection into a certain number of groups.
1081     *
1082     * @param  int  $numberOfGroups
1083     * @return static
1084     */
1085    public function split($numberOfGroups)
1086    {
1087        if ($this->isEmpty()) {
1088            return new static;
1089        }
1090
1091        $groups = new static;
1092
1093        $groupSize = floor($this->count() / $numberOfGroups);
1094
1095        $remain = $this->count() % $numberOfGroups;
1096
1097        $start = 0;
1098
1099        for ($i = 0; $i < $numberOfGroups; $i++) {
1100            $size = $groupSize;
1101
1102            if ($i < $remain) {
1103                $size++;
1104            }
1105
1106            if ($size) {
1107                $groups->push(new static(array_slice($this->items, $start, $size)));
1108
1109                $start += $size;
1110            }
1111        }
1112
1113        return $groups;
1114    }
1115
1116    /**
1117     * Split a collection into a certain number of groups, and fill the first groups completely.
1118     *
1119     * @param  int  $numberOfGroups
1120     * @return static
1121     */
1122    public function splitIn($numberOfGroups)
1123    {
1124        return $this->chunk(ceil($this->count() / $numberOfGroups));
1125    }
1126
1127    /**
1128     * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception.
1129     *
1130     * @param  mixed  $key
1131     * @param  mixed  $operator
1132     * @param  mixed  $value
1133     * @return mixed
1134     *
1135     * @throws \Illuminate\Support\ItemNotFoundException
1136     * @throws \Illuminate\Support\MultipleItemsFoundException
1137     */
1138    public function sole($key = null, $operator = null, $value = null)
1139    {
1140        $filter = func_num_args() > 1
1141            ? $this->operatorForWhere(...func_get_args())
1142            : $key;
1143
1144        $items = $this->when($filter)->filter($filter);
1145
1146        if ($items->isEmpty()) {
1147            throw new ItemNotFoundException;
1148        }
1149
1150        if ($items->count() > 1) {
1151            throw new MultipleItemsFoundException;
1152        }
1153
1154        return $items->first();
1155    }
1156
1157    /**
1158     * Get the first item in the collection but throw an exception if no matching items exist.
1159     *
1160     * @param  mixed  $key
1161     * @param  mixed  $operator
1162     * @param  mixed  $value
1163     * @return mixed
1164     *
1165     * @throws \Illuminate\Support\ItemNotFoundException
1166     */
1167    public function firstOrFail($key = null, $operator = null, $value = null)
1168    {
1169        $filter = func_num_args() > 1
1170            ? $this->operatorForWhere(...func_get_args())
1171            : $key;
1172
1173        $placeholder = new stdClass();
1174
1175        $item = $this->first($filter, $placeholder);
1176
1177        if ($item === $placeholder) {
1178            throw new ItemNotFoundException;
1179        }
1180
1181        return $item;
1182    }
1183
1184    /**
1185     * Chunk the collection into chunks of the given size.
1186     *
1187     * @param  int  $size
1188     * @return static
1189     */
1190    public function chunk($size)
1191    {
1192        if ($size <= 0) {
1193            return new static;
1194        }
1195
1196        $chunks = [];
1197
1198        foreach (array_chunk($this->items, $size, true) as $chunk) {
1199            $chunks[] = new static($chunk);
1200        }
1201
1202        return new static($chunks);
1203    }
1204
1205    /**
1206     * Chunk the collection into chunks with a callback.
1207     *
1208     * @param  callable  $callback
1209     * @return static
1210     */
1211    public function chunkWhile(callable $callback)
1212    {
1213        return new static(
1214            $this->lazy()->chunkWhile($callback)->mapInto(static::class)
1215        );
1216    }
1217
1218    /**
1219     * Sort through each item with a callback.
1220     *
1221     * @param  callable|int|null  $callback
1222     * @return static
1223     */
1224    public function sort($callback = null)
1225    {
1226        $items = $this->items;
1227
1228        $callback && is_callable($callback)
1229            ? uasort($items, $callback)
1230            : asort($items, $callback ?? SORT_REGULAR);
1231
1232        return new static($items);
1233    }
1234
1235    /**
1236     * Sort items in descending order.
1237     *
1238     * @param  int  $options
1239     * @return static
1240     */
1241    public function sortDesc($options = SORT_REGULAR)
1242    {
1243        $items = $this->items;
1244
1245        arsort($items, $options);
1246
1247        return new static($items);
1248    }
1249
1250    /**
1251     * Sort the collection using the given callback.
1252     *
1253     * @param  callable|array|string  $callback
1254     * @param  int  $options
1255     * @param  bool  $descending
1256     * @return static
1257     */
1258    public function sortBy($callback, $options = SORT_REGULAR, $descending = false)
1259    {
1260        if (is_array($callback) && ! is_callable($callback)) {
1261            return $this->sortByMany($callback);
1262        }
1263
1264        $results = [];
1265
1266        $callback = $this->valueRetriever($callback);
1267
1268        // First we will loop through the items and get the comparator from a callback
1269        // function which we were given. Then, we will sort the returned values and
1270        // and grab the corresponding values for the sorted keys from this array.
1271        foreach ($this->items as $key => $value) {
1272            $results[$key] = $callback($value, $key);
1273        }
1274
1275        $descending ? arsort($results, $options)
1276            : asort($results, $options);
1277
1278        // Once we have sorted all of the keys in the array, we will loop through them
1279        // and grab the corresponding model so we can set the underlying items list
1280        // to the sorted version. Then we'll just return the collection instance.
1281        foreach (array_keys($results) as $key) {
1282            $results[$key] = $this->items[$key];
1283        }
1284
1285        return new static($results);
1286    }
1287
1288    /**
1289     * Sort the collection using multiple comparisons.
1290     *
1291     * @param  array  $comparisons
1292     * @return static
1293     */
1294    protected function sortByMany(array $comparisons = [])
1295    {
1296        $items = $this->items;
1297
1298        usort($items, function ($a, $b) use ($comparisons) {
1299            foreach ($comparisons as $comparison) {
1300                $comparison = Arr::wrap($comparison);
1301
1302                $prop = $comparison[0];
1303
1304                $ascending = Arr::get($comparison, 1, true) === true ||
1305                             Arr::get($comparison, 1, true) === 'asc';
1306
1307                $result = 0;
1308
1309                if (is_callable($prop)) {
1310                    $result = $prop($a, $b);
1311                } else {
1312                    $values = [data_get($a, $prop), data_get($b, $prop)];
1313
1314                    if (! $ascending) {
1315                        $values = array_reverse($values);
1316                    }
1317
1318                    $result = $values[0] <=> $values[1];
1319                }
1320
1321                if ($result === 0) {
1322                    continue;
1323                }
1324
1325                return $result;
1326            }
1327        });
1328
1329        return new static($items);
1330    }
1331
1332    /**
1333     * Sort the collection in descending order using the given callback.
1334     *
1335     * @param  callable|string  $callback
1336     * @param  int  $options
1337     * @return static
1338     */
1339    public function sortByDesc($callback, $options = SORT_REGULAR)
1340    {
1341        return $this->sortBy($callback, $options, true);
1342    }
1343
1344    /**
1345     * Sort the collection keys.
1346     *
1347     * @param  int  $options
1348     * @param  bool  $descending
1349     * @return static
1350     */
1351    public function sortKeys($options = SORT_REGULAR, $descending = false)
1352    {
1353        $items = $this->items;
1354
1355        $descending ? krsort($items, $options) : ksort($items, $options);
1356
1357        return new static($items);
1358    }
1359
1360    /**
1361     * Sort the collection keys in descending order.
1362     *
1363     * @param  int  $options
1364     * @return static
1365     */
1366    public function sortKeysDesc($options = SORT_REGULAR)
1367    {
1368        return $this->sortKeys($options, true);
1369    }
1370
1371    /**
1372     * Splice a portion of the underlying collection array.
1373     *
1374     * @param  int  $offset
1375     * @param  int|null  $length
1376     * @param  mixed  $replacement
1377     * @return static
1378     */
1379    public function splice($offset, $length = null, $replacement = [])
1380    {
1381        if (func_num_args() === 1) {
1382            return new static(array_splice($this->items, $offset));
1383        }
1384
1385        return new static(array_splice($this->items, $offset, $length, $this->getArrayableItems($replacement)));
1386    }
1387
1388    /**
1389     * Take the first or last {$limit} items.
1390     *
1391     * @param  int  $limit
1392     * @return static
1393     */
1394    public function take($limit)
1395    {
1396        if ($limit < 0) {
1397            return $this->slice($limit, abs($limit));
1398        }
1399
1400        return $this->slice(0, $limit);
1401    }
1402
1403    /**
1404     * Take items in the collection until the given condition is met.
1405     *
1406     * @param  mixed  $value
1407     * @return static
1408     */
1409    public function takeUntil($value)
1410    {
1411        return new static($this->lazy()->takeUntil($value)->all());
1412    }
1413
1414    /**
1415     * Take items in the collection while the given condition is met.
1416     *
1417     * @param  mixed  $value
1418     * @return static
1419     */
1420    public function takeWhile($value)
1421    {
1422        return new static($this->lazy()->takeWhile($value)->all());
1423    }
1424
1425    /**
1426     * Transform each item in the collection using a callback.
1427     *
1428     * @param  callable  $callback
1429     * @return $this
1430     */
1431    public function transform(callable $callback)
1432    {
1433        $this->items = $this->map($callback)->all();
1434
1435        return $this;
1436    }
1437
1438    /**
1439     * Return only unique items from the collection array.
1440     *
1441     * @param  string|callable|null  $key
1442     * @param  bool  $strict
1443     * @return static
1444     */
1445    public function unique($key = null, $strict = false)
1446    {
1447        $callback = $this->valueRetriever($key);
1448
1449        $exists = [];
1450
1451        return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) {
1452            if (in_array($id = $callback($item, $key), $exists, $strict)) {
1453                return true;
1454            }
1455
1456            $exists[] = $id;
1457        });
1458    }
1459
1460    /**
1461     * Reset the keys on the underlying array.
1462     *
1463     * @return static
1464     */
1465    public function values()
1466    {
1467        return new static(array_values($this->items));
1468    }
1469
1470    /**
1471     * Zip the collection together with one or more arrays.
1472     *
1473     * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]);
1474     *      => [[1, 4], [2, 5], [3, 6]]
1475     *
1476     * @param  mixed  ...$items
1477     * @return static
1478     */
1479    public function zip($items)
1480    {
1481        $arrayableItems = array_map(function ($items) {
1482            return $this->getArrayableItems($items);
1483        }, func_get_args());
1484
1485        $params = array_merge([function () {
1486            return new static(func_get_args());
1487        }, $this->items], $arrayableItems);
1488
1489        return new static(array_map(...$params));
1490    }
1491
1492    /**
1493     * Pad collection to the specified length with a value.
1494     *
1495     * @param  int  $size
1496     * @param  mixed  $value
1497     * @return static
1498     */
1499    public function pad($size, $value)
1500    {
1501        return new static(array_pad($this->items, $size, $value));
1502    }
1503
1504    /**
1505     * Get an iterator for the items.
1506     *
1507     * @return \ArrayIterator
1508     */
1509    #[\ReturnTypeWillChange]
1510    public function getIterator()
1511    {
1512        return new ArrayIterator($this->items);
1513    }
1514
1515    /**
1516     * Count the number of items in the collection.
1517     *
1518     * @return int
1519     */
1520    #[\ReturnTypeWillChange]
1521    public function count()
1522    {
1523        return count($this->items);
1524    }
1525
1526    /**
1527     * Count the number of items in the collection by a field or using a callback.
1528     *
1529     * @param  callable|string  $countBy
1530     * @return static
1531     */
1532    public function countBy($countBy = null)
1533    {
1534        return new static($this->lazy()->countBy($countBy)->all());
1535    }
1536
1537    /**
1538     * Add an item to the collection.
1539     *
1540     * @param  mixed  $item
1541     * @return $this
1542     */
1543    public function add($item)
1544    {
1545        $this->items[] = $item;
1546
1547        return $this;
1548    }
1549
1550    /**
1551     * Get a base Support collection instance from this collection.
1552     *
1553     * @return \Illuminate\Support\Collection
1554     */
1555    public function toBase()
1556    {
1557        return new self($this);
1558    }
1559
1560    /**
1561     * Determine if an item exists at an offset.
1562     *
1563     * @param  mixed  $key
1564     * @return bool
1565     */
1566    #[\ReturnTypeWillChange]
1567    public function offsetExists($key)
1568    {
1569        return isset($this->items[$key]);
1570    }
1571
1572    /**
1573     * Get an item at a given offset.
1574     *
1575     * @param  mixed  $key
1576     * @return mixed
1577     */
1578    #[\ReturnTypeWillChange]
1579    public function offsetGet($key)
1580    {
1581        return $this->items[$key];
1582    }
1583
1584    /**
1585     * Set the item at a given offset.
1586     *
1587     * @param  mixed  $key
1588     * @param  mixed  $value
1589     * @return void
1590     */
1591    #[\ReturnTypeWillChange]
1592    public function offsetSet($key, $value)
1593    {
1594        if (is_null($key)) {
1595            $this->items[] = $value;
1596        } else {
1597            $this->items[$key] = $value;
1598        }
1599    }
1600
1601    /**
1602     * Unset the item at a given offset.
1603     *
1604     * @param  string  $key
1605     * @return void
1606     */
1607    #[\ReturnTypeWillChange]
1608    public function offsetUnset($key)
1609    {
1610        unset($this->items[$key]);
1611    }
1612}
1613