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