Streams provide a fluent interface to transform arrays and iterables through a pipeline of operations.
Streams are made up of:
- One stream source factory method to create the stream.
- Zero or more stream operators that transform the stream to a new stream.
- Terminal operation of either:
- Stream terminal operation to transform the stream to a value or data structure.
$result = Stream::of([1, 1, 2, 2, 3, 4, 5]) ->distinct() // [1, 2, 3, 4, 5] ->map(fn ($x) => $x**2) // [1, 4, 9, 16, 25] ->filter(fn ($x) => $x < 10) // [1, 4, 9] ->toSum(); // 14
- The stream is iterated via a
foreachloop.
$result = Stream::of([1, 1, 2, 2, 3, 4, 5]) ->distinct() // [1, 2, 3, 4, 5] ->map(fn ($x) => $x**2) // [1, 4, 9, 16, 25] ->filter(fn ($x) => $x < 10); // [1, 4, 9] foreach ($result as $item) { // 1, 4, 9 }
Creates stream from an iterable.
Stream::of(iterable $iterable): Stream
use IterTools\Stream;
$iterable = [1, 2, 3];
$result = Stream::of($iterable)
->chainWith([4, 5, 6], [7, 8, 9])
->zipEqualWith([1, 2, 3, 4, 5, 6, 7, 8, 9])
->toValue(fn ($carry, $item) => $carry + array_sum($item));
// 90Creates stream of n random coin flips.
Stream::ofCoinFlips(int $repetitions): Stream
use IterTools\Stream;
$result = Stream::ofCoinFlips(10)
->filterTrue()
->toCount();
// 5 (random)Creates a stream of rows of a CSV file.
Stream::ofCsvFile(resource $fileHandle, string $separator = ',', string $enclosure = '"', string = $escape = '\\'): Stream
use IterTools\Stream;
$fileHandle = \fopen('path/to/file.csv', 'r');
$result = Stream::of($fileHandle)
->toArray();Creates a stream of rows of a CSV file as associative arrays keyed by header.
Stream::ofCsvFileAssoc(resource $fileHandle, ?array $headers = null, string $separator = ',', string $enclosure = '"', string $escape = '\\'): Stream
If $headers is null, the first row of the file is consumed and used as the header list. Otherwise, the supplied $headers are used and every row is treated as data.
use IterTools\Stream;
$fileHandle = \fopen('path/to/file.csv', 'r');
$result = Stream::ofCsvFileAssoc($fileHandle)
->toArray();
// [['header1' => 'value1', ...], ...]Creates stream of nothing.
Stream::ofEmpty(): Stream
use IterTools\Stream;
$result = Stream::ofEmpty()
->chainWith([1, 2, 3])
->toArray();
// 1, 2, 3Creates a stream of lines of a file.
Stream::ofFileLines(resource $fileHandle): Stream
use IterTools\Stream;
$fileHandle = \fopen('path/to/file.txt', 'r');
$result = Stream::of($fileHandle)
->map('strtoupper');
->toArray();Creates stream of random selections from an array of values.
Stream::ofRandomChoice(array $items, int $repetitions): Stream
use IterTools\Stream;
$languages = ['PHP', 'Go', 'Python'];
$languages = Stream::ofRandomChoice($languages, 5)
->toArray();
// 'Go', 'PHP', 'Python', 'PHP', 'PHP' (random)Creates stream of random numbers (integers).
Stream::ofRandomNumbers(int $min, int $max, int $repetitions): Stream
use IterTools\Stream;
$min = 1;
$max = 3;
$reps = 7;
$result = Stream::ofRandomNumbers($min, $max, $reps)
->toArray();
// 1, 2, 2, 1, 3, 2, 1 (random)Creates stream of random percentages between 0 and 1.
Stream::ofRandomPercentage(int $repetitions): Stream
use IterTools\Stream;
$stream = Stream::ofRandomPercentage(3)
->toArray();
// 0.8012566976245, 0.81237281724151, 0.61676896329459 [random]Creates a lazy stream of a finite arithmetic progression of numbers.
Stream::ofRange(int|float|string $start, int|float|string $end, int|float $step = 1): Stream
- Numeric strings (e.g.
"1","1.5","1e3","01") are accepted and coerced toint/floatbefore iteration. Non-numeric strings (e.g."a") throw\InvalidArgumentException. - Direction is inferred from
$startvs$end. A negative$stepis accepted only when the direction is descending. - Throws
\InvalidArgumentExceptionwhen any operand is non-finite (including numeric strings such as"1e309"that overflow toINF), when$step == 0, when the step sign conflicts with the operand direction, or whenabs($step) > abs($end - $start). - Lazy — safe to use with large bounds combined with downstream operations such as
limit.
use IterTools\Stream;
$numbers = Stream::ofRange(0, 5)
->toArray();
// 0, 1, 2, 3, 4, 5
$descending = Stream::ofRange(5, 1)
->toArray();
// 5, 4, 3, 2, 1
$firstFive = Stream::ofRange(1, PHP_INT_MAX)
->limit(5)
->toArray();
// 1, 2, 3, 4, 5Note: prior to this release,
Stream::ofRangematerialized the full sequence eagerly via\range()and, when given two numeric-string inputs without leading zeros, preserved string-typed output (e.g.ofRange("1", "5")yielded["1", ..., "5"]). The lazy rewrite normalizes all numeric-string inputs toint/floatuniformly. It also pins native\range()'s PHP 8.3+ numeric semantics on every supported PHP version, so an integer-valued float step on int operands now yields ints (ofRange(1, 5, 1.0)→[1, 2, 3, 4, 5]) where PHP 8.2 previously yielded floats.
Creates stream of rock-paper-scissors hands.
Stream::ofRockPaperScissors(int $repetitions): Stream
use IterTools\Stream;
$rps = Stream::ofRockPaperScissors(5)
->toArray();
// 'paper', 'rock', 'rock', 'scissors', 'paper' [random]Accumulate the running result of applying a binary operator across the stream.
$stream->accumulate(callable $op, mixed ...$initial): Stream
- Without an initial value: the first yielded element is the first datum unchanged, and each subsequent element is
$op(accumulator, nextDatum). - With an initial value: the first yielded element is the initial value, and each subsequent element is
$op(accumulator, nextDatum). - Explicit
nullis a legitimate initial value. - Throws
\InvalidArgumentExceptionif more than one initial value is passed.
use IterTools\Stream;
$numbers = [1, 2, 3, 4, 5];
$runningSums = Stream::of($numbers)
->accumulate(fn ($a, $b) => $a + $b)
->toArray();
// [1, 3, 6, 10, 15]
$withInitial = Stream::of($numbers)
->accumulate(fn ($a, $b) => $a + $b, 100)
->toArray();
// [100, 101, 103, 106, 110, 115]Sorts the stream, maintaining keys.
$stream->asort(?callable $comparator = null)
If comparator is not provided, the elements of the iterable source must be comparable.
use IterTools\Stream;
$worldPopulations = [
'China' => 1_439_323_776,
'India' => 1_380_004_385,
'Indonesia' => 273_523_615,
'USA' => 331_002_651,
];
$result = Stream::of($worldPopulations)
->filter(fn ($pop) => $pop > 300_000_000)
->asort()
->toAssociativeArray();
// USA => 331_002_651,
// India => 1_380_004_385,
// China => 1_439_323_776,Return a stream chaining additional sources together into a single consecutive stream.
$stream->chainWith(iterable ...$iterables): Stream
use IterTools\Stream;
$input = [1, 2, 3];
$result = Stream::of($input)
->chainWith([4, 5, 6])
->chainWith([7, 8, 9])
->toArray();
// 1, 2, 3, 4, 5, 6, 7, 8, 9Cartesian product of the stream with zero or more additional iterables.
$stream->productWith(iterable ...$iterables): Stream
Tuples are list arrays (0-indexed, in input order). With zero extra iterables, each stream element is wrapped in a one-element tuple. If any iterable (stream or extra) is empty, the result is empty.
Note: Passing the same non-rewindable iterator instance more than once is not supported.
use IterTools\Stream;
$numbers = [1, 2];
$letters = ['a', 'b'];
$result = Stream::of($numbers)
->productWith($letters)
->toArray();
// [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]Permutations of the stream's elements.
$stream->permutations(?int $r = null): Stream
Tuples are list arrays (0-indexed, in input order). Output order follows Python's itertools.permutations (lexicographic by input position): duplicate values are position-unique. $r = 0 yields one empty tuple; $r greater than the stream length yields nothing; $r = null means full-length permutations.
Throws \InvalidArgumentException if $r is negative.
use IterTools\Stream;
$data = [1, 2, 3];
$result = Stream::of($data)
->permutations(2)
->toArray();
// [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]Combinations (without replacement) of the stream's elements.
$stream->combinations(int $r): Stream
Tuples are list arrays (0-indexed, in input order). Output order follows Python's itertools.combinations (lexicographic by input position): duplicate values are position-unique. $r = 0 yields one empty tuple; $r greater than the stream length yields nothing.
Throws \InvalidArgumentException if $r is negative.
use IterTools\Stream;
$data = [1, 2, 3, 4];
$result = Stream::of($data)
->combinations(2)
->toArray();
// [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]Combinations with replacement of the stream's elements.
$stream->combinationsWithReplacement(int $r): Stream
Tuples are list arrays (0-indexed, in input order). Output order follows Python's itertools.combinations_with_replacement (lexicographic by input position): duplicate values are position-unique and may produce duplicate output tuples. Unlike combinations(), $r may exceed the stream length — elements repeat. $r = 0 yields one empty tuple.
Throws \InvalidArgumentException if $r is negative.
use IterTools\Stream;
$data = [1, 2, 3];
$result = Stream::of($data)
->combinationsWithReplacement(2)
->toArray();
// [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]]Every subset of the stream's elements, ordered by length then by input position.
$stream->powerset(): Stream
Subsets are list arrays (0-indexed, in input order); source keys are discarded. Subsets are yielded in length-ascending order; within each length the order matches Stream::combinations (lexicographic by input position): duplicate values are position-unique. Empty stream yields one empty subset.
Warning: a stream of
nelements yields2**nsubsets — consumption grows exponentially.
use IterTools\Stream;
$data = [1, 2, 3];
$result = Stream::of($data)
->powerset()
->toArray();
// [[], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]use IterTools\Stream;
// Every combination of feature flags to drive parameterized tests.
$result = Stream::of(['darkMode', 'beta', 'analytics'])
->powerset()
->toArray();
// [[], ['darkMode'], ['beta'], ['analytics'],
// ['darkMode', 'beta'], ['darkMode', 'analytics'], ['beta', 'analytics'],
// ['darkMode', 'beta', 'analytics']]See also Combinatorics::powerset.
Compress to a new stream by filtering out data that is not selected.
$stream->compress(iterable $selectors): Stream
Selectors indicate which data. True value selects item. False value filters out data.
use IterTools\Stream;
$input = [1, 2, 3];
$result = Stream::of($input)
->compress([0, 1, 1])
->toArray();
// 2, 3Compress to a new stream by filtering out keys that are not selected.
$stream->compressAssociative(array $keys): Stream
- Standard PHP array/iterator keys only (string, integer).
use IterTools\Stream;
$starWarsEpisodes = [
'I' => 'The Phantom Menace',
'II' => 'Attack of the Clones',
'III' => 'Revenge of the Sith',
'IV' => 'A New Hope',
'V' => 'The Empire Strikes Back',
'VI' => 'Return of the Jedi',
'VII' => 'The Force Awakens',
'VIII' => 'The Last Jedi',
'IX' => 'The Rise of Skywalker',
];
$sequelTrilogyNumbers = ['VII', 'VIII', 'IX'];
$sequelTrilogy = Stream::of($starWarsEpisodes)
->compressAssociative($sequelTrilogyNumbers)
->toAssociativeArray();
// 'VII' => 'The Force Awakens',
// 'VIII' => 'The Last Jedi',
// 'IX' => 'The Rise of Skywalker',Return a stream consisting of chunks of elements from the stream.
$stream->chunkwise(int $chunkSize): Stream
Chunk size must be at least 1.
use IterTools\Stream;
$friends = ['Ross', 'Rachel', 'Chandler', 'Monica', 'Joey'];
$result = Stream::of($friends)
->chunkwise(2)
->toArray();
// ['Ross', 'Rachel'], ['Chandler', 'Monica'], ['Joey']Return a stream consisting of overlapping chunks of elements from the stream.
$stream->chunkwiseOverlap(int $chunkSize, int $overlapSize, bool $includeIncompleteTail = true): Stream
- Chunk size must be at least 1.
- Overlap size must be less than chunk size.
- See also Windowed — a step-based sibling that additionally supports gapped windows (
$step > $size).
use IterTools\Stream;
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
$result = Stream::of($friends)
->chunkwiseOverlap(3, 1)
->toArray()
// [1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9]Return a stream filtering out elements from the stream only returning distinct elements.
$stream->distinct(bool $strict = true): Stream
Defaults to strict type comparisons. Set strict to false for type coercion comparisons.
use IterTools\Stream;
$input = [1, 2, 1, 2, 3, 3, '1', '1', '2', '3'];
$stream = Stream::of($input)
->distinct()
->toArray();
// 1, 2, 3, '1', '2', '3'
$stream = Stream::of($input)
->distinct(false)
->toArray();
// 1, 2, 3Return a stream filtering out elements from the stream only returning distinct elements according to a custom comparator function.
$stream->distinctBy(callable $compareBy): Stream
use IterTools\Stream;
$streetFighterConsoleReleases = [
['id' => '112233', 'name' => 'Street Fighter 3 3rd Strike', 'console' => 'Dreamcast'],
['id' => '223344', 'name' => 'Street Fighter 3 3rd Strike', 'console' => 'PS4'],
['id' => '334455', 'name' => 'Street Fighter 3 3rd Strike', 'console' => 'PS5'],
['id' => '445566', 'name' => 'Street Fighter VI', 'console' => 'PS4'],
['id' => '556677', 'name' => 'Street Fighter VI', 'console' => 'PS5'],
['id' => '667799', 'name' => 'Street Fighter VI', 'console' => 'PC'],
];
$stream = Stream::of($streetFighterConsoleReleases)
->distinctBy(fn ($sfTitle) => $sfTitle['name'])
->toArray();
// Contains one SF3 3rd Strike entry and one SFVI entryReturn a stream that removes only consecutive duplicates (Unix uniq behavior).
$stream->distinctAdjacent(): Stream
- Each element is compared strictly (
===) to the previous element yielded. - Non-adjacent duplicates are kept.
- Runs in O(1) memory — only the previous element is held.
- Source keys are discarded.
use IterTools\Stream;
$result = Stream::of([1, 1, 2, 2, 3, 1, 1])
->distinctAdjacent()
->toArray();
// [1, 2, 3, 1]use IterTools\Stream;
$logLines = ['error: timeout', 'error: timeout', 'info: ok', 'error: timeout', 'error: timeout'];
$collapsed = Stream::of($logLines)
->distinctAdjacent()
->toArray();
// ['error: timeout', 'info: ok', 'error: timeout']See also Set::distinctAdjacent.
Return a stream that removes only consecutive duplicates by key, using a custom key function.
$stream->distinctAdjacentBy(callable $keyFn): Stream
- Each element's extracted key is compared strictly (
===) to the previous element's key. - Non-adjacent duplicate keys are kept.
- Runs in O(1) memory and calls
$keyFnonce per element. - Source keys are discarded.
use IterTools\Stream;
$words = ['apple', 'ant', 'banana', 'berry', 'apple'];
$firstLetterRuns = Stream::of($words)
->distinctAdjacentBy(fn ($s) => $s[0])
->toArray();
// ['apple', 'banana', 'apple']use IterTools\Stream;
$readings = [
['ts' => 60, 'v' => 1],
['ts' => 65, 'v' => 2],
['ts' => 119, 'v' => 3],
['ts' => 120, 'v' => 4],
['ts' => 121, 'v' => 5],
];
$compressed = Stream::of($readings)
->distinctAdjacentBy(fn ($r) => intdiv($r['ts'], 60))
->toArray();
// [['ts' => 60, 'v' => 1], ['ts' => 120, 'v' => 4]]See also Set::distinctAdjacentBy.
Iterate all elements of the stream except the last $count.
$stream->dropLast(int $count): Stream
Single pass, queue-based: the trailing $count elements are never yielded. If $count is 0, all elements are yielded; if $count is greater than or equal to the stream length, nothing is yielded. Keys are preserved.
use IterTools\Stream;
$rows = ['row 1', 'row 2', 'row 3', 'TOTAL'];
$result = Stream::of($rows)
->dropLast(1)
->toArray();
// row 1, row 2, row 3Drop elements from the stream while the predicate function is true.
$stream->dropWhile(callable $predicate): Stream
Once the predicate function returns false once, all remaining elements are returned.
use IterTools\Stream;
$input = [1, 2, 3, 4, 5]
$result = Stream::of($input)
->dropWhile(fn ($value) => $value < 3)
->toArray();
// 3, 4, 5Yield [index, value] pairs from the stream.
$stream->enumerate(int $start = 0): Stream
- The index is sequential starting from
$start, independent of the source iterable's keys. - Negative
$startis allowed.
use IterTools\Stream;
$seasons = ['spring', 'summer', 'autumn', 'winter'];
$result = Stream::of($seasons)
->enumerate()
->toArray();
// [[0, 'spring'], [1, 'summer'], [2, 'autumn'], [3, 'winter']]Filter out elements from the stream only keeping elements where the predicate function is true.
$stream->filter(callable $predicate): Stream
use IterTools\Stream;
$input = [1, -1, 2, -2, 3, -3];
$result = Stream::of($input)
->filter(fn ($value) => $value > 0)
->toArray();
// 1, 2, 3Filter out elements from the stream only keeping elements that are truthy. If a predicate is provided, keep only the elements for which it returns true.
$stream->filterTrue(?callable $predicate = null): Stream
use IterTools\Stream;
$input = [0, 1, 2, 3, 0, 4];
$result = Stream::of($input)
->filterTrue()
->toArray();
// 1, 2, 3, 4Filter out elements from the stream only keeping elements that are falsy. If a predicate is provided, keep only the elements for which it returns false.
$stream->filterFalse(?callable $predicate = null): Stream
use IterTools\Stream;
$input = [0, 1, 2, 3, 0, 4];
$result = Stream::of($input)
->filterFalse()
->toArray();
// 0, 0Filter out elements from the stream only keeping elements where the predicate function on the keys is true.
$stream->filterKeys(callable $filter): Stream
$olympics = [
2000 => 'Sydney',
2002 => 'Salt Lake City',
2004 => 'Athens',
2006 => 'Turin',
2008 => 'Beijing',
2010 => 'Vancouver',
2012 => 'London',
2014 => 'Sochi',
2016 => 'Rio de Janeiro',
2018 => 'Pyeongchang',
2020 => 'Tokyo',
2022 => 'Beijing',
];
$winterFilter = fn ($year) => $year % 4 === 2;
$result = Stream::of($olympics)
->filterKeys($winterFilter)
->toAssociativeArray();
}
// 2002 => Salt Lake City
// 2006 => Turin
// 2010 => Vancouver
// 2014 => Sochi
// 2018 => Pyeongchang
// 2022 => BeijingFilter the stream, passing both the value and the key to the predicate.
Keeps elements for which the predicate—called as $predicate($value, $key)—is truthy (coerced via (bool)). Keys are preserved.
$stream->filterWithKeys(callable $predicate): Stream
$inventory = ['apples' => 5, 'bananas' => 0, 'avocados' => 3, 'cherries' => 0];
$inStockStartingWithA = fn ($count, $name) => $count > 0 && \str_starts_with($name, 'a');
$result = Stream::of($inventory)
->filterWithKeys($inStockStartingWithA)
->toAssociativeArray();
// ['apples' => 5, 'avocados' => 3]Map a function onto the elements of the stream and flatten the results.
$stream->flatMap(callable $mapper): Stream
$data = [1, 2, 3, 4, 5];
$mapper fn ($item) => ($item % 2 === 0) ? [$item, $item] : $item;
$result = Stream::of($data)
->flatMap($mapper)
->toArray();
// [1, 2, 2, 3, 4, 4, 5]Map a key-aware function onto the elements of the stream and flatten the results by one level.
The callback is called as $func($value, $key, callable $self). The third argument is the function itself, enabling recursive flat-mapping over nested iterables. Outer and inner keys are discarded—the result is yielded with auto-generated sequential numeric keys.
$stream->flatMapWithKeys(callable $func): Stream
$data = ['a' => 1, 'b' => 2, 'c' => 3];
$func = fn ($value, $key) => [$key, $value];
$result = Stream::of($data)
->flatMapWithKeys($func)
->toArray();
// ['a', 1, 'b', 2, 'c', 3]Flatten a multidimensional stream.
$stream->flatten(int $dimensions = 1): Stream
$data = [1, [2, 3], [4, 5]];
$result = Stream::of($data)
->flatten($mapper)
->toArray();
// [1, 2, 3, 4, 5]Frequency distribution of the stream elements.
$stream->frequencies(bool $strict = true): Stream
use IterTools\Stream;
$grades = ['A', 'A', 'B', 'B', 'B', 'C'];
$result = Stream::of($grades)
->frequencies()
->toAssociativeArray();
// ['A' => 2, 'B' => 3, 'C' => 1]Frequency distribution of the stream elements grouped by the value returned from the key function.
$stream->frequenciesBy(callable $keyFunc, bool $strict = true): Stream
The key function must return an int or string; any other return type throws a \TypeError when the stream is consumed.
use IterTools\Stream;
$words = ['apple', 'pear', 'banana', 'kiwi'];
$result = Stream::of($words)
->frequenciesBy(fn ($word) => \strlen($word))
->toAssociativeArray();
// [5 => 1, 4 => 2, 6 => 1]Return a stream grouping by a common data element.
$stream->groupBy(callable $groupKeyFunction, callable $itemKeyFunction = null): Stream
- The
$groupKeyFunctiondetermines the key to group elements by. - The optional
$itemKeyFunctionallows custom indexes within each group member.
use IterTools\Stream;
$input = [1, -1, 2, -2, 3, -3];
$groups = Stream::of($input)
->groupBy(fn ($item) => $item > 0 ? 'positive' : 'negative');
foreach ($groups as $group => $item) {
// 'positive' => [1, 2, 3], 'negative' => [-1, -2, -3]
}Return a stream cycling through the elements of stream sequentially forever.
$stream->infiniteCycle(): Stream
use IterTools\Stream;
$input = [1, 2, 3];
$result = Stream::of($input)
->infiniteCycle()
->print();
// 1, 2, 3, 1, 2, 3, ...Return a stream intersecting the stream with the input iterables.
$stream->intersectionWith(iterable ...$iterables): Stream
use IterTools\Stream;
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
$numerics = ['1', '2', 3, 4, 5, 6, 7, '8', '9'];
$oddNumbers = [1, 3, 5, 7, 9, 11];
$stream = Stream::of($numbers)
->intersectionWith($numerics, $oddNumbers)
->toArray();
// 3, 5, 7Return a stream intersecting the stream with the input iterables using type coercion.
$stream->intersectionCoerciveWith(iterable ...$iterables): Stream
use IterTools\Stream;
$languages = ['php', 'python', 'c++', 'java', 'c#', 'javascript', 'typescript'];
$scriptLanguages = ['php', 'python', 'javascript', 'typescript'];
$supportsInterfaces = ['php', 'java', 'c#', 'typescript'];
$stream = Stream::of($languages)
->intersectionCoerciveWith($scriptLanguages, $supportsInterfaces)
->toArray();
// 'php', 'typescript'Return a stream with a separator inserted between consecutive elements.
$stream->intersperse(mixed $separator): Stream
- No separator is emitted before the first element or after the last element.
- The separator is yielded as-is on each pass: arrays are not expanded, objects retain identity.
- Source keys are discarded — the output is a list with sequential integer keys.
use IterTools\Stream;
$flow = '';
foreach (Stream::of(['fetch', 'parse', 'validate', 'persist'])->intersperse(' -> ') as $part) {
$flow .= $part;
}
// 'fetch -> parse -> validate -> persist'use IterTools\Stream;
$row = '';
foreach (Stream::of(['name', 'email', 'role'])->intersperse(',') as $part) {
$row .= $part;
}
// 'name,email,role'See also Single::intersperse.
Return a stream up to a limit.
Stops even if more data available if limit reached.
Lazy: the upstream is never advanced beyond the elements that are yielded. A limit of 0 does not touch the upstream at all.
$stream->limit(int $limit): Stream
Use IterTools\Single;
$matrixMovies = ['The Matrix', 'The Matrix Reloaded', 'The Matrix Revolutions', 'The Matrix Resurrections'];
$limit = 1;
$goodMovies = Stream::of($matrixMovies)
->limit($limit)
->toArray();
// 'The Matrix' (and nothing else)Return a stream containing the result of mapping a function onto each element of the stream.
$stream->map(callable $function): Stream
use IterTools\Stream;
$grades = [100, 95, 98, 89, 100];
$result = Stream::of($grades)
->map(fn ($grade) => $grade === 100 ? 'A' : 'F')
->toArray();
// A, F, F, F, AReturn a stream containing the result of mapping a function onto each element of the stream, passing both the value and the key to the callback.
The callback is called as $func($value, $key). The transformed value is yielded with its original key preserved.
$stream->mapWithKeys(callable $func): Stream
use IterTools\Stream;
$prices = ['apple' => 1.5, 'banana' => 0.75, 'cherry' => 3.0];
$result = Stream::of($prices)
->mapWithKeys(fn ($price, $name) => "$name: \$$price")
->toAssociativeArray();
// ['apple' => 'apple: $1.5', 'banana' => 'banana: $0.75', 'cherry' => 'cherry: $3']Return a stream containing the result of mapping a function onto each element of the stream, unpacking each element positionally as arguments.
$stream->mapSpread(callable $function): Stream
- Each element of the stream must itself be iterable; its values are splatted into
$functionpositionally. - Inner keys are discarded — values flow positionally even when an inner element is an associative array.
- Outer keys are preserved (matching
Stream::map). - Throws
\InvalidArgumentExceptionif any inner element is not iterable.
use IterTools\Stream;
$result = Stream::of([[1, 2], [3, 4], [5, 6]])
->mapSpread(fn ($a, $b) => $a + $b)
->toArray();
// [3, 7, 11]use IterTools\Stream;
$names = ['Alice', 'Bob', 'Carol'];
$scores = [92, 87, 95];
$result = Stream::of($names)
->zipWith($scores)
->mapSpread(fn (string $name, int $score) => "{$name}: {$score}")
->toArray();
// ['Alice: 92', 'Bob: 87', 'Carol: 95']See also Single::mapSpread.
Return a stream consisting of pairs of elements from the stream.
$stream->pairwise(): Stream
Returns empty stream if given collection contains less than 2 elements.
use IterTools\Stream;
$input = [1, 2, 3, 4, 5];
$stream = Stream::of($input)
->pairwise()
->toArray();
// [1, 2], [2, 3], [3, 4], [4, 5]Return a stream partially intersecting the stream with the input iterables.
$stream->partialIntersectionWith(int $minIntersectionCount, iterable ...$iterables): Stream
use IterTools\Stream;
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
$numerics = ['1', '2', 3, 4, 5, 6, 7, '8', '9'];
$oddNumbers = [1, 3, 5, 7, 9, 11];
$stream = Stream::of($numbers)
->partialIntersectionWith($numerics, $oddNumbers)
->toArray();
// 1, 3, 4, 5, 6, 7, 9Return a stream partially intersecting the stream with the input iterables using type coercion.
$stream->partialIntersectionCoerciveWith(int $minIntersectionCount, iterable ...$iterables): Stream
use IterTools\Stream;
$languages = ['php', 'python', 'c++', 'java', 'c#', 'javascript', 'typescript'];
$scriptLanguages = ['php', 'python', 'javascript', 'typescript'];
$supportsInterfaces = ['php', 'java', 'c#', 'typescript'];
$stream = Stream::of($languages)
->partialIntersectionCoerciveWith(2, $scriptLanguages, $supportsInterfaces)
->toArray();
// 'php', 'python', 'java', 'typescript', 'c#', 'javascript'Return a new stream of key-value elements reindexed by the key indexer function.
$stream->reindex(callable $indexer): Stream
use IterTools\Stream;
$data = [
[
'title' => 'Star Wars: Episode IV – A New Hope',
'episode' => 'IV',
'year' => 1977,
],
[
'title' => 'Star Wars: Episode V – The Empire Strikes Back',
'episode' => 'V',
'year' => 1980,
],
[
'title' => 'Star Wars: Episode VI – Return of the Jedi',
'episode' => 'VI',
'year' => 1983,
],
];
$reindexFunc = fn (array $swFilm) => $swFilm['episode'];
$reindexResult = Stream::of($data)
->reindex($reindexFunc)
->toAssociativeArray();
// [
// 'IV' => [
// 'title' => 'Star Wars: Episode IV – A New Hope',
// 'episode' => 'IV',
// 'year' => 1977,
// ],
// 'V' => [
// 'title' => 'Star Wars: Episode V – The Empire Strikes Back',
// 'episode' => 'V',
// 'year' => 1980,
// ],
// 'VI' => [
// 'title' => 'Star Wars: Episode VI – Return of the Jedi',
// 'episode' => 'VI',
// 'year' => 1983,
// ],
// ]Relative frequency distribution of the stream elements.
$stream->relativeFrequencies(bool $strict = true): Stream
use IterTools\Stream;
$grades = ['A', 'A', 'B', 'B', 'B', 'C'];
$result = Stream::of($grades)
->relativeFrequencies()
->toAssociativeArray();
// A => 0.33, B => 0.5, C => 0.166Relative frequency distribution of the stream elements grouped by the value returned from the key function.
$stream->relativeFrequenciesBy(callable $keyFunc, bool $strict = true): Stream
The key function must return an int or string; any other return type throws a \TypeError when the stream is consumed.
use IterTools\Stream;
$words = ['apple', 'pear', 'kiwi', 'plum'];
$result = Stream::of($words)
->relativeFrequenciesBy(fn ($word) => \strlen($word))
->toAssociativeArray();
// [5 => 0.25, 4 => 0.75]Reverse the elements of a stream.
$stream->reverse(): Stream
use IterTools\Stream;
$words = ['are', 'you', 'as' ,'bored', 'as', 'I', 'am'];
$reversed = Stream::of($words)
->reverse()
->toString(' ');
// am I as bored as you areReturn a stream that yields one value at a time from the stream and the given iterables in round-robin order.
$stream->roundRobinWith(iterable ...$iterables): Stream
On each round, takes one value from each iterable that still has values; once an iterable is
exhausted, it is skipped in subsequent rounds. Iteration ends when every iterable is exhausted.
Unlike zipWith, values are yielded individually rather than as tuples. Source keys are discarded;
the output is sequentially re-indexed.
use IterTools\Stream;
$result = Stream::of(['A', 'B', 'C'])
->roundRobinWith(['D', 'E'], ['F', 'G', 'H'])
->toArray();
// ['A', 'D', 'F', 'B', 'E', 'G', 'C', 'H']Round-robin scheduling fairly drains items from worker queues of unequal size:
$workerOne = ['task-1', 'task-4', 'task-7'];
$workerTwo = ['task-2', 'task-5'];
$workerThree = ['task-3', 'task-6', 'task-8', 'task-9'];
$schedule = Stream::of($workerOne)
->roundRobinWith($workerTwo, $workerThree)
->toArray();
// ['task-1', 'task-2', 'task-3', 'task-4', 'task-5', 'task-6', 'task-7', 'task-8', 'task-9']See also Multi::roundRobin.
Return a stream accumulating the running average (mean) over the stream.
$stream->runningAverage(int|float|null $initialValue = null): Stream
use IterTools\Stream;
$input = [1, 3, 5];
$result = Stream::of($input)
->runningAverage()
->toArray();
// 1, 2, 3Return a stream accumulating the running difference over the stream.
$stream->runningDifference(int|float|null $initialValue = null): Stream
use IterTools\Stream;
$input = [1, 2, 3, 4, 5];
$result = Stream::of($input)
->runningDifference()
->toArray();
// -1, -3, -6, -10, -15Return a stream accumulating the running max over the stream.
$stream->runningMax(int|float|null $initialValue = null): Stream
use IterTools\Stream;
$input = [1, -1, 2, -2, 3, -3];
$result = Stream::of($input)
->runningMax()
->toArray();
// 1, 1, 2, 2, 3, 3Return a stream accumulating the running min over the stream.
$stream->runningMin(int|float|null $initialValue = null): Stream
use IterTools\Stream;
$input = [1, -1, 2, -2, 3, -3];
$result = Stream::of($input)
->runningMin()
->toArray();
// 1, -1, -1, -2, -2, -3Return a stream accumulating the running product over the stream.
$stream->runningProduct(int|float|null $initialValue = null): Stream
use IterTools\Stream;
$input = [1, 2, 3, 4, 5];
$result = Stream::of($input)
->runningProduct()
->toArray();
// 1, 2, 6, 24, 120Return a stream accumulating the running total over the stream.
$stream->runningTotal(int|float|null $initialValue = null): Stream
use IterTools\Stream;
$input = [1, 2, 3, 4, 5];
$result = Stream::of($input)
->runningTotal()
->toArray();
// 1, 3, 6, 10, 15Skip some elements of the stream.
$stream->skip(int $count, int $offset = 0): Stream
use IterTools\Stream;
$movies = [
'The Phantom Menace', 'Attack of the Clones', 'Revenge of the Sith',
'A New Hope', 'The Empire Strikes Back', 'Return of the Jedi',
'The Force Awakens', 'The Last Jedi', 'The Rise of Skywalker'
];
$onlyTheBest = Stream::of($movies)
->skip(3)
->skip(3, 3)
->toArray();
// 'A New Hope', 'The Empire Strikes Back', 'Return of the Jedi'Extract a slice of the stream.
Lazy: the upstream is never advanced beyond the last element that is yielded. A count of 0 does not touch the upstream at all. Elements skipped by $start or $step must still be pulled in order to be skipped over.
$stream->slice(int $start = 0, int $count = null, int $step = 1)
use IterTools\Stream;
$olympics = [1992, 1994, 1996, 1998, 2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2018, 2020, 2022];
$summerOlympics = Stream::of($olympics)
->slice(0, 8, 2)
->toArray();
// [1992, 1996, 2000, 2004, 2008, 2012, 2016, 2020]Sorts the stream.
$stream->sort(callable $comparator = null)
If comparator is not provided, the elements of the iterable source must be comparable.
use IterTools\Stream;
$input = [3, 4, 5, 9, 8, 7, 1, 6, 2];
$result = Stream::of($input)
->sort()
->toArray();
// 1, 2, 3, 4, 5, 6, 7, 8, 9Sorts the stream using a key-extraction function (Schwartzian transform).
$stream->sortBy(callable $keyFn)
The key function is called exactly once per element. Source keys are discarded. The sort is stable: elements with equal extracted keys preserve their original relative order.
See also Sort::sortBy().
use IterTools\Stream;
$words = ['banana', 'fig', 'cherry', 'apple'];
$result = Stream::of($words)
->sortBy(fn (string $s) => \strlen($s))
->toArray();
// ['fig', 'apple', 'banana', 'cherry']use IterTools\Stream;
$people = [
(object)['name' => 'Alice', 'age' => 30],
(object)['name' => 'Bob', 'age' => 20],
(object)['name' => 'Carol', 'age' => 40],
];
$names = Stream::of($people)
->sortBy(fn ($p) => $p->age)
->map(fn ($p) => $p->name)
->toArray();
// ['Bob', 'Alice', 'Carol']Sorts the stream using a key-extraction function (Schwartzian transform), maintaining keys.
$stream->asortBy(callable $keyFn)
The key function is called exactly once per element. Source keys are preserved. The sort is stable: elements with equal extracted keys preserve their original relative order.
See also Sort::asortBy().
use IterTools\Stream;
$scores = [
'Alice' => 87,
'Bob' => 92,
'Carol' => 75,
];
$result = Stream::of($scores)
->asortBy(fn (int $score) => $score)
->toAssociativeArray();
// ['Carol' => 75, 'Alice' => 87, 'Bob' => 92]use IterTools\Stream;
$people = [
'alice' => (object)['age' => 30],
'bob' => (object)['age' => 20],
'carol' => (object)['age' => 40],
];
$result = Stream::of($people)
->asortBy(fn ($p) => $p->age)
->toAssociativeArray();
// ['bob' => (object)['age' => 20], 'alice' => (object)['age' => 30], 'carol' => (object)['age' => 40]]Reduces the stream to the n largest elements (descending order).
$stream->largest(int $n, callable $keyFn = null)
Uses a bounded heap of size n internally — the full stream is never sorted.
Stable under ties; skips NaN keys; throws \InvalidArgumentException for negative $n.
See also Sort::largest().
use IterTools\Stream;
$data = [3, 1, 4, 1, 5, 9, 2, 6];
$result = Stream::of($data)
->largest(3)
->toArray();
// [9, 6, 5]use IterTools\Stream;
$leaderboard = [
(object)['name' => 'Alice', 'score' => 87],
(object)['name' => 'Bob', 'score' => 92],
(object)['name' => 'Carol', 'score' => 75],
(object)['name' => 'Dave', 'score' => 95],
(object)['name' => 'Eve', 'score' => 90],
];
$result = Stream::of($leaderboard)
->largest(3, fn ($p) => $p->score)
->map(fn ($p) => $p->name)
->toArray();
// ['Dave', 'Bob', 'Eve']Reduces the stream to the n smallest elements (ascending order).
$stream->smallest(int $n, callable $keyFn = null)
Uses a bounded heap of size n internally — the full stream is never sorted.
Stable under ties; skips NaN keys; throws \InvalidArgumentException for negative $n.
See also Sort::smallest().
use IterTools\Stream;
$data = [3, 1, 4, 1, 5, 9, 2, 6];
$result = Stream::of($data)
->smallest(3)
->toArray();
// [1, 1, 2]use IterTools\Stream;
$requests = [
(object)['id' => 'r1', 'durationMs' => 120],
(object)['id' => 'r2', 'durationMs' => 50],
(object)['id' => 'r3', 'durationMs' => 200],
(object)['id' => 'r4', 'durationMs' => 80],
(object)['id' => 'r5', 'durationMs' => 65],
];
$result = Stream::of($requests)
->smallest(3, fn ($r) => $r->durationMs)
->map(fn ($r) => $r->id)
->toArray();
// ['r2', 'r5', 'r4']Return a stream of the difference of the stream and the given iterables. Elements from the source not present in any given iterables.
$stream->differenceWith(iterable ...$iterables): Stream
Note: If input iterables produce duplicate items, then multiset difference rules apply.
use IterTools\Stream;
$a = [1, 2, 3, 4, 7];
$b = [2, 3, 5, 8];
$c = [1, 6, 9];
$stream = Stream::of($a)
->differenceWith($b, $c)
->toArray();
// 4, 7Return a stream of the difference of the stream and the given iterables using type coercion.
$stream->differenceCoerciveWith(iterable ...$iterables): Stream
Note: If input iterables produce duplicate items, then multiset difference rules apply.
use IterTools\Stream;
$a = [1, 2, 3, 4, 7];
$b = ['1', 2, 3, 5, 8];
$stream = Stream::of($a)
->differenceCoerciveWith($b)
->toArray();
// 4, 7Return a stream of the symmetric difference of the stream and the given iterables.
$stream->symmetricDifferenceWith(iterable ...$iterables): Stream
Note: If input iterables produce duplicate items, then multiset intersection rules apply.
use IterTools\Stream;
$a = [1, 2, 3, 4, 7];
$b = ['1', 2, 3, 5, 8];
$c = [1, 2, 3, 6, 9];
$stream = Stream::of($a)
->symmetricDifferenceWith($b, $c)
->toArray();
// '1', 4, 5, 6, 7, 8, 9Return a stream of the symmetric difference of the stream and the given iterables using type coercion.
$stream->symmetricDifferenceCoerciveWith(iterable ...$iterables): Stream
Note: If input iterables produce duplicate items, then multiset intersection rules apply.
use IterTools\Stream;
$a = [1, 2, 3, 4, 7];
$b = ['1', 2, 3, 5, 8];
$c = [1, 2, 3, 6, 9];
$stream = Stream::of($a)
->symmetricDifferenceCoerciveWith($b, $c)
->toArray();
// 4, 5, 6, 7, 8, 9Iterate the last $count elements of the stream.
$stream->takeLast(int $count): Stream
Lazy-but-bounded: only a ring buffer of size $count is held in memory. If $count is 0, nothing is yielded; if $count is greater than the stream length, all elements are yielded. Keys are preserved.
use IterTools\Stream;
$logLines = ['line 1', 'line 2', 'line 3', 'line 4', 'line 5'];
$result = Stream::of($logLines)
->takeLast(2)
->toArray();
// line 4, line 5Keep elements from the stream as long as the predicate is true.
$stream->takeWhile(callable $predicate): Stream
If no predicate is provided, the boolean value of the data is used.
use IterTools\Stream;
$input = [1, -1, 2, -2, 3, -3];
$result = Stream::of($input)
->takeWhile(fn ($value) => abs($value) < 3)
->toArray();
// 1, -1, 2, -2Return a stream consisting of the union of the stream and the input iterables.
$stream->unionWith(iterable ...$iterables): Stream
Note: If input iterables produce duplicate items, then multiset union rules apply.
use IterTools\Stream;
$input = [1, 2, 3];
$stream = Stream::of($input)
->unionWith([3, 4, 5, 6])
->toArray();
// [1, 2, 3, 4, 5, 6]Return a stream consisting of the union of the stream and the input iterables using type coercion.
$stream->unionCoerciveWith(iterable ...$iterables): Stream
Note: If input iterables produce duplicate items, then multiset union rules apply.
use IterTools\Stream;
$input = [1, 2, 3];
$stream = Stream::of($input)
->unionCoerciveWith(['3', 4, 5, 6])
->toArray();
// [1, 2, 3, 4, 5, 6]Treat the stream itself as a sequence of rows and transpose into columns — the inverse of zip.
$stream->unzip(): Stream
Yields one column array per index up to the width of the shortest row. The outer stream and every row are fully consumed when the unzipped stream is iterated, before the first column can be yielded — column 0 cannot be emitted until every row's first cell is known.
use IterTools\Stream;
$pairs = [[1, 'a'], [2, 'b'], [3, 'c']];
$columns = Stream::of($pairs)
->unzip()
->toArray();
// [[1, 2, 3], ['a', 'b', 'c']]Splitting (timestamp, value) event tuples into two parallel series:
use IterTools\Stream;
$events = [
[1700000000, 12.5],
[1700000060, 13.1],
[1700000120, 12.9],
];
[$timestamps, $values] = Stream::of($events)
->unzip()
->toArray();
// $timestamps === [1700000000, 1700000060, 1700000120]
// $values === [12.5, 13.1, 12.9]Treat the stream itself as a sequence of iterables and zip them column-wise (transpose).
$stream->zip(): Stream
For uneven lengths, iteration stops when the shortest row is exhausted. Similar to Python's zip(*rows) idiom.
use IterTools\Stream;
$rows = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
$stream = Stream::of($rows)
->zip()
->toArray();
// [[1, 4, 7], [2, 5, 8], [3, 6, 9]]Pairs well with chunkwise() — chunk a flat stream into groups, then transpose:
use IterTools\Stream;
$stream = Stream::of([1, 2, 3, 4, 5, 6])
->chunkwise(3)
->zip()
->toArray();
// [[1, 4], [2, 5], [3, 6]]Pairs naturally with toPartition() — build tournament matchups by pairing the top half against the reversed bottom half (1 vs 8, 2 vs 7, 3 vs 6, 4 vs 5):
use IterTools\Stream;
[$topHalf, $bottomHalf] = Stream::of([1, 2, 3, 4, 5, 6, 7, 8])
->toPartition(fn (int $seed): bool => $seed <= 4);
$matchups = Stream::of([$topHalf, array_reverse($bottomHalf)])
->zip()
->toArray();
// [[1, 8], [2, 7], [3, 6], [4, 5]]Transpose a row-oriented table into columns — handy when records arrive row-by-row but you need each field as its own series:
use IterTools\Stream;
$rows = [
['Alice', 30, 'NYC'],
['Bob', 25, 'LA'],
['Carol', 41, 'Austin'],
];
$columns = Stream::of($rows)
->zip()
->toArray();
// [['Alice', 'Bob', 'Carol'], [30, 25, 41], ['NYC', 'LA', 'Austin']]The outer stream must be finite; it is consumed when the zipped stream is iterated, before the first tuple is yielded. Inner rows are advanced lazily after that. Passing the same iterator instance more than once is not supported and may not behave as expected.
Treat the stream itself as a sequence of iterables and zip them column-wise (transpose), continuing until the longest row is exhausted.
$stream->zipLongest(): Stream
For uneven lengths, the exhausted rows will produce null for the remaining iterations. Similar to Python's zip_longest(*rows) idiom.
use IterTools\Stream;
$rows = [[1, 2, 3], [4, 5]];
$stream = Stream::of($rows)
->zipLongest()
->toArray();
// [[1, 4], [2, 5], [3, null]]Group month-by-month readings across years with uneven record lengths — short years surface as null gaps instead of dropping data:
use IterTools\Stream;
$rainfallByYear = [
[3.2, 4.1, 5.0, 6.2], // 2022
[2.8, 3.9, 4.7], // 2023 — sensor outage mid-year
[3.5, 4.3, 5.2, 6.8, 7.1], // 2024
];
$byMonth = Stream::of($rainfallByYear)
->zipLongest()
->toArray();
// [[3.2, 2.8, 3.5], [4.1, 3.9, 4.3], [5.0, 4.7, 5.2], [6.2, null, 6.8], [null, null, 7.1]]The outer stream must be finite; it is consumed when the zipped stream is iterated, before the first tuple is yielded. Inner rows are advanced lazily after that. Passing the same iterator instance more than once is not supported and may not behave as expected.
Treat the stream itself as a sequence of iterables and zip them column-wise (transpose), continuing until the longest row is exhausted, using a filler value for missing entries.
$stream->zipFilled(mixed $filler): Stream
For uneven lengths, the exhausted rows will produce $filler for the remaining iterations.
use IterTools\Stream;
$rows = [[1, 2, 3], [4, 5]];
$stream = Stream::of($rows)
->zipFilled('?')
->toArray();
// [[1, 4], [2, 5], [3, '?']]Useful when a caller downstream wants a numeric default instead of null — e.g. quarterly sales figures across teams where missing quarters should count as zero for aggregation:
use IterTools\Stream;
$salesByTeam = [
[120, 150, 180, 210], // Team A — full year
[ 95, 110], // Team B — onboarded H2
[140, 160, 175], // Team C — Q4 pending
];
$byQuarter = Stream::of($salesByTeam)
->zipFilled(0)
->toArray();
// [[120, 95, 140], [150, 110, 160], [180, 0, 175], [210, 0, 0]]The outer stream must be finite; it is consumed when the zipped stream is iterated, before the first tuple is yielded. Inner rows are advanced lazily after that. Passing the same iterator instance more than once is not supported and may not behave as expected.
Treat the stream itself as a sequence of iterables of equal lengths and zip them column-wise (transpose).
$stream->zipEqual(): Stream
Works like Stream::zip() but throws \LengthException if row lengths are not equal — i.e., at least one row ends before the others. Use this when equal lengths are a required invariant.
use IterTools\Stream;
$rows = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
$stream = Stream::of($rows)
->zipEqual()
->toArray();
// [[1, 4, 7], [2, 5, 8], [3, 6, 9]]A natural fit for CSV-style records where every row must have the same number of fields — transposing to columns surfaces a schema violation as a \LengthException instead of silently truncating or padding:
use IterTools\Stream;
$records = [
['id', 'name', 'email'],
[1, 'Alice', 'alice@example.com'],
[2, 'Bob', 'bob@example.com'],
];
$byField = Stream::of($records)
->zipEqual()
->toArray();
// [['id', 1, 2], ['name', 'Alice', 'Bob'], ['email', 'alice@example.com', 'bob@example.com']]The outer stream must be finite; it is consumed when the zipped stream is iterated, before the first tuple is yielded. Inner rows are advanced lazily after that. Passing the same iterator instance more than once is not supported and may not behave as expected.
Return a stream consisting of multiple iterable collections streamed simultaneously.
$stream->zipWith(iterable ...$iterables): Stream
For uneven lengths, iterations stops when the shortest iterable is exhausted.
use IterTools\Stream;
$input = [1, 2, 3];
$stream = Stream::of($input)
->zipWith([4, 5, 6])
->zipWith([7, 8, 9])
->toArray();
// [1, 4, 7], [2, 5, 8], [3, 6, 9]Return a stream consisting of multiple iterable collections, using a default filler value if lengths no equal.
$stream->zipFilledWith(mixed $default, iterable ...$iterables): Stream
use IterTools\Stream;
$input = [1, 2, 3];
$stream = Stream::of($input)
->zipFilledWith('?', ['A', 'B']);
foreach ($stream as $zipped) {
// [1, A], [2, B], [3, ?]
}Return a stream consisting of multiple iterable collections of equal lengths streamed simultaneously.
$stream->zipEqualWith(iterable ...$iterables): Stream
Works like Stream::zipWith() method but throws \LengthException if lengths not equal,
i.e., at least one iterator ends before the others.
use IterTools\Stream;
$input = [1, 2, 3];
$stream = Stream::of($input)
->zipEqualWith([4, 5, 6])
->zipEqualWith([7, 8, 9]);
foreach ($stream as $zipped) {
// [1, 4, 7], [2, 5, 8], [3, 6, 9]
}Return a stream consisting of multiple iterable collections streamed simultaneously.
$stream->zipLongestWith(iterable ...$iterables): Stream
- Iteration continues until the longest iterable is exhausted.
- For uneven lengths, the exhausted iterables will produce null for the remaining iterations.
use IterTools\Stream;
$input = [1, 2, 3, 4, 5];
$stream = Stream::of($input)
->zipLongestWith([4, 5, 6])
->zipLongestWith([7, 8, 9, 10]);
foreach ($stream as $zipped) {
// [1, 4, 7], [2, 5, 8], [3, 6, 9], [4, null, 10], [null, null, 5]
}Append values to the end of the stream.
$stream->append(mixed ...$values): Stream
- For appending an entire iterable, use
chainWith. - Output keys are reindexed sequentially across the entire stream.
- Empty variadic leaves the stream unchanged.
use IterTools\Stream;
$result = Stream::of([1, 2])->append(3, 4)->toArray();
// [1, 2, 3, 4]Prepend values to the front of the stream.
$stream->prepend(mixed ...$values): Stream
- For prepending an entire iterable, use
chainWith. - Output keys are reindexed sequentially across the entire stream.
- Empty variadic leaves the stream unchanged.
use IterTools\Stream;
$result = Stream::of([3, 4])->prepend(1, 2)->toArray();
// [1, 2, 3, 4]Split the stream into groups, starting a new group every time $predicate matches.
$stream->splitWhen(callable $predicate): Stream
The matching element starts the next group. Source keys are discarded; outer is sequential, inner groups are list arrays.
use IterTools\Stream;
$result = Stream::of([1, 2, 0, 3, 0, 4])
->splitWhen(fn ($x) => $x === 0)
->toArray();
// [[1, 2], [0, 3], [0, 4]]Group adjacent elements that share a key returned by $keyFn.
$stream->groupAdjacentBy(callable $keyFn): Stream
Yields [groupKey, list<value>] pairs sequentially. Repeated keys appearing in non-adjacent runs produce separate groups.
use IterTools\Stream;
$result = Stream::of([1, 1, 2, 2, 1])
->groupAdjacentBy(fn ($x) => $x)
->toArray();
// [[1, [1, 1]], [2, [2, 2]], [1, [1]]]Pad the stream on the left so its length is at least $length.
$stream->padLeft(int $length, mixed $fill): Stream
- If the stream is already
$lengthor longer, all elements pass through unchanged. - Source keys are discarded; output keys are sequential 0-indexed.
- Throws
\InvalidArgumentExceptionif$lengthis negative.
use IterTools\Stream;
$result = Stream::of([1, 2, 3])->padLeft(5, 0)->toArray();
// [0, 0, 1, 2, 3]Pad the stream on the right so its length is at least $length.
$stream->padRight(int $length, mixed $fill): Stream
- If the stream is already
$lengthor longer, all elements pass through unchanged. - Source keys are discarded; output keys are sequential 0-indexed.
- Throws
\InvalidArgumentExceptionif$lengthis negative.
use IterTools\Stream;
$result = Stream::of([1, 2, 3])->padRight(5, 0)->toArray();
// [1, 2, 3, 0, 0]Yield each duplicated value once, at the moment its second occurrence is observed.
$stream->duplicates(bool $strict = true): Stream
use IterTools\Stream;
$result = Stream::of([1, 2, 1, 1, 2, 3])->duplicates()->toArray();
// [1, 2]Yield each value whose extracted key duplicates a previously seen key, once at the moment of the second occurrence.
$stream->duplicatesBy(callable $keyFn): Stream
use IterTools\Stream;
$users = [
['id' => 1, 'name' => 'Alice'],
['id' => 2, 'name' => 'Bob'],
['id' => 1, 'name' => 'Alicia'],
];
$result = Stream::of($users)
->duplicatesBy(fn ($u) => $u['id'])
->toArray();
// [['id' => 1, 'name' => 'Alicia']]Randomize the order of the stream.
$stream->shuffle(?\Random\Engine $engine = null): Stream
- Materializes the stream.
- Source keys are discarded; output keys are sequential 0-indexed.
use IterTools\Stream;
$result = Stream::of([1, 2, 3, 4, 5])->shuffle()->toArray();
// e.g.: [3, 1, 5, 4, 2]Sample $size elements from the stream without replacement.
$stream->sample(int $size, ?\Random\Engine $engine = null): Stream
- Materializes the stream. Output keys are sequential 0-indexed.
- Throws
\InvalidArgumentExceptionif$sizeis negative. - Throws
\LengthExceptionif$sizeexceeds the stream length.
use IterTools\Stream;
$result = Stream::of([1, 2, 3, 4, 5])->sample(3)->toArray();
// e.g.: [4, 1, 5]Sample up to $size elements from the stream uniformly at random in a single pass (Algorithm R).
$stream->reservoirSample(int $size, ?\Random\Engine $engine = null): Stream
- Unlike the lazy
sample, this operation is eager: it consumes the upstream immediately at call time and binds the resulting array as the new stream source. - When
$size >=the stream length, the entire stream is returned in original order with zero random draws. Output keys are sequential 0-indexed. - Throws
\InvalidArgumentExceptionif$sizeis negative.
use IterTools\Stream;
$result = Stream::of([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
->reservoirSample(3)
->toArray();
// e.g.: [2, 7, 9]Iterate sliding windows of $size elements from the stream, advancing $step elements between windows.
$stream->windowed(int $size, int $step = 1, bool $partial = false): Stream
- Each window is a 0-indexed list array; source keys are discarded. Supports gapped windows (
$step > $size). - For
1 <= $step <= $size, this is equivalent tochunkwiseOverlap($size, $size - $step, includeIncompleteTail: $partial)(see Chunkwise Overlap). $partialcontrols whether a final incomplete window is emitted. Note: it defaults tofalse— the opposite ofchunkwiseOverlap'sincludeIncompleteTaildefault oftrue.- Throws
\InvalidArgumentExceptionif$size < 1or$step < 1.
use IterTools\Stream;
$result = Stream::of([1, 2, 3, 4, 5])
->windowed(3)
->toArray();
// [[1, 2, 3], [2, 3, 4], [3, 4, 5]]Pair every element of the stream with a boolean flag marking whether it is the first element.
$stream->withFirst(): Stream
Yields [bool $isFirst, mixed $value] tuples. Source keys are discarded; output keys are sequential 0-indexed.
use IterTools\Stream;
$result = Stream::of(['a', 'b', 'c'])
->withFirst()
->toArray();
// [[true, 'a'], [false, 'b'], [false, 'c']]Pair every element of the stream with a boolean flag marking whether it is the last element.
$stream->withLast(): Stream
Yields [bool $isLast, mixed $value] tuples. Source keys are discarded; output keys are sequential 0-indexed.
use IterTools\Stream;
$result = Stream::of(['a', 'b', 'c'])
->withLast()
->toArray();
// [[false, 'a'], [false, 'b'], [true, 'c']]Pair every element of the stream with flags marking whether it is the first and/or last element.
$stream->withFirstAndLast(): Stream
Yields [bool $isFirst, bool $isLast, mixed $value] tuples. A single-element stream yields one [true, true, $value] tuple. Source keys are discarded; output keys are sequential 0-indexed.
use IterTools\Stream;
$result = Stream::of(['a', 'b', 'c'])
->withFirstAndLast()
->toArray();
// [[true, false, 'a'], [false, false, 'b'], [false, true, 'c']]Returns true if all elements match the predicate function.
$stream->allMatch(callable $predicate): bool
use IterTools\Summary;
$finalFantasyNumbers = [4, 5, 6];
$isOnSuperNintendo = fn ($ff) => $ff >= 4 && $ff <= 6;
$boolean = Stream::of($finalFantasyNumbers)
->allMatch($isOnSuperNintendo);
// trueReturns true if all elements are unique.
$stream->allUnique(bool $strict = true): bool
Defaults to strict type comparisons. Set strict to false for type coercion comparisons.
use IterTools\Summary;
$items = ['fingerprints', 'snowflakes', 'eyes', 'DNA']
$boolean = Stream::of($items)
->allUnique();
// trueReturns true if any element matches the predicate function.
$stream->anyMatch(callable $predicate): bool
use IterTools\Summary;
$answers = ['fish', 'towel', 42, "don't panic"];
$isUltimateAnswer = fn ($a) => a == 42;
$boolean = Stream::of($answers)
->anyMatch($answers, $isUltimateAnswer);
// trueReturns true if all iterables are permutations with stream.
$stream->arePermutationsWith(...$iterables): bool
use IterTools\Summary;
$rite = ['r', 'i', 't', 'e'];
$reit = ['r', 'e', 'i', 't'];
$tier = ['t', 'i', 'e', 'r'];
$tire = ['t', 'i', 'r', 'e'];
$trie = ['t', 'r', 'i', 'e'];
$boolean = Stream::of(['i', 't', 'e', 'r'])
->arePermutationsWith($rite, $reit, $tier, $tire, $trie);
// trueReturns true if at least n items in the stream match the predicate function.
- Predicate is optional.
- Default predicate is the boolean value of each item.
- Short-circuits as soon as the count reaches n.
n <= 0always returns true.
$stream->atLeastN(int $n, callable $predicate = null): bool
use IterTools\Stream;
$grades = [45, 50, 61, 72, 85];
$isPassingGrade = fn ($grade) => $grade >= 70;
$boolean = Stream::of($grades)->atLeastN(2, $isPassingGrade);
// trueReturns true if at most n items in the stream match the predicate function.
- Predicate is optional.
- Default predicate is the boolean value of each item.
- Short-circuits as soon as the count exceeds n.
n < 0always returns false.
$stream->atMostN(int $n, callable $predicate = null): bool
use IterTools\Stream;
$attempts = [false, false, true, false];
$isFailure = fn ($attempt) => $attempt === false;
$boolean = Stream::of($attempts)->atMostN(3, $isFailure);
// trueReturns true if all iterables are permutations with stream with type coercion.
$stream->arePermutationsCoerciveWith(...$iterables): bool
use IterTools\Summary;
$set2 = [2.0, '1', 3];
$set3 = [3, 2, 1];
$boolean = Stream::of([1, 2.0, '3'])
->arePermutationsCoerciveWith($set2, $set3);
// trueReturns true if the stream contains the needle using strict-type comparison.
$stream->contains(mixed $needle): bool
- Scalars are compared strictly by type (
1does not match'1';0does not matchfalse). - Objects match only the same instance.
- Arrays are compared with
===. NaNnever matchesNaN.- Short-circuits on the first match.
use IterTools\Stream;
$primes = [2, 3, 5, 7, 11, 13];
$boolean = Stream::of($primes)->contains(7);
// true
$boolean = Stream::of($primes)->contains('7');
// false (strict comparison)Returns true if the stream contains the needle using type coercion.
$stream->containsCoercive(mixed $needle): bool
- Scalars are compared non-strictly by value (
1matches'1';0matchesfalse;'1e2'matches100). - Objects are compared by serialized value (throws
\InvalidArgumentExceptionif needle or any visited datum is not serializable). - Arrays are compared by serialized value.
NaNmatchesNaN.- Short-circuits on the first match.
use IterTools\Stream;
$primes = [2, 3, 5, 7, 11, 13];
$boolean = Stream::of($primes)->containsCoercive('7');
// true (coercive comparison)Returns true if the stream ends with the given suffix using strict-type comparison.
- Compares values pairwise; keys are ignored.
- Empty suffix returns true without consuming the stream.
- The stream must be finite.
$stream->endsWith(iterable $suffix): bool
use IterTools\Stream;
$path = ['var', 'log', 'nginx', 'access.log'];
$boolean = Stream::of($path)->endsWith(['nginx', 'access.log']);
// true
$boolean = Stream::of($path)->endsWith(['error.log']);
// falseReturns true if the stream ends with the given suffix using type coercion.
- Compares values pairwise; keys are ignored.
- Empty suffix returns true without consuming the stream.
- The stream must be finite.
- Scalars are compared non-strictly by value, objects and arrays by serialized value, and
NaNmatchesNaN. - Throws
\InvalidArgumentExceptionif a non-serializable object is reached during comparison.
$stream->endsWithCoercive(iterable $suffix): bool
use IterTools\Stream;
$digits = [1, 2, 3];
$boolean = Stream::of($digits)->endsWithCoercive(['2', '3']);
// true (coercive comparison)Returns true if exactly n items are true according to a predicate function.
- Predicate is optional.
- Default predicate is boolean value of each item.
$stream->exactlyN(int $n, callable $predicate = null): bool
use IterTools\Summary;
$twoTruthsAndALie = [true, true, false];
$n = 2;
$boolean = Stream::of($twoTruthsAndALie)->exactlyN($n);
// trueReturns true if the stream is empty having no items.
$stream->isEmpty(): bool
use IterTools\Summary;
$numbers = [0, 1, 2, 3, 4, 5];
$filterFunc = fn ($x) => $x > 10;
$boolean = Stream::($numbers)
->filter($filterFunc)
->isEmpty();
// trueReturns true if all elements of given collection that satisfy the predicate appear before all elements that don't.
- Returns true for empty collection or for collection with single item.
- Default predicate if not provided is the boolean value of each data item.
$stream->isPartitioned(callable $predicate = null): bool
use IterTools\Summary;
$numbers = [0, 2, 4, 1, 3, 5];
$evensBeforeOdds = fn ($item) => $item % 2 === 0;
$boolean = Stream::($numbers)
->isPartitioned($evensBeforeOdds);
// trueReturns true if iterable source is sorted in ascending order; otherwise false.
$stream->isSorted(): bool
Items of iterable source must be comparable.
Returns true if iterable source is empty or has only one element.
use IterTools\Stream;
$input = [1, 2, 3, 4, 5];
$result = Stream::of($input)
->isSorted();
// true
$input = [1, 2, 3, 2, 1];
$result = Stream::of($input)
->isSorted();
// falseReturns true if iterable source is sorted in reverse descending order; otherwise false.
$stream->isReversed(): bool
Items of iterable source must be comparable.
Returns true if iterable source is empty or has only one element.
use IterTools\Stream;
$input = [5, 4, 3, 2, 1];
$result = Stream::of($input)
->isReversed();
// true
$input = [1, 2, 3, 2, 1];
$result = Stream::of($input)
->isReversed();
// falseReturns true if the values projected by the key function are sorted in non-decreasing (ascending) order; otherwise false.
$stream->isSortedBy(callable $keyFunc): bool
Projected values must be comparable.
Returns true if iterable source is empty or has only one element.
use IterTools\Stream;
$people = [
(object)['name' => 'Alice', 'age' => 25],
(object)['name' => 'Bob', 'age' => 30],
(object)['name' => 'Carol', 'age' => 42],
];
$result = Stream::of($people)
->isSortedBy(fn ($person) => $person->age);
// trueReturns true if the values projected by the key function are sorted in non-increasing (reverse descending) order; otherwise false.
$stream->isReversedBy(callable $keyFunc): bool
Projected values must be comparable.
Returns true if iterable source is empty or has only one element.
use IterTools\Stream;
$people = [
(object)['name' => 'Carol', 'age' => 42],
(object)['name' => 'Bob', 'age' => 30],
(object)['name' => 'Alice', 'age' => 25],
];
$result = Stream::of($people)
->isReversedBy(fn ($person) => $person->age);
// trueReturns true if no element matches the predicate function.
$stream->noneMatch(callable $predicate): bool
use IterTools\Summary;
$grades = [45, 50, 61, 0];
$isPassingGrade = fn ($grade) => $grade >= 70;
$boolean = Stream::of($grades)->noneMatch($isPassingGrade);
// trueReturns true if iterable source and all given collections are the same.
$stream->sameWith(iterable ...$iterables): bool
For empty iterables list returns true.
use IterTools\Stream;
$input = [1, 2, 3, 4, 5];
$result = Stream::of($input)
->sameWith([1, 2, 3, 4, 5]);
// true
$result = Stream::of($input)
->sameWith([5, 4, 3, 2, 1]);
// falseReturns true if iterable source and all given collections have the same lengths.
$stream->sameCountWith(iterable ...$iterables): bool
For empty iterables list returns true.
use IterTools\Stream;
$input = [1, 2, 3, 4, 5];
$result = Stream::of($input)
->sameCountWith([5, 4, 3, 2, 1]);
// true
$result = Stream::of($input)
->sameCountWith([1, 2, 3]);
// falseReturns true if the stream starts with the given prefix using strict-type comparison.
- Compares values pairwise; keys are ignored.
- Empty prefix returns true without consuming the stream.
$stream->startsWith(iterable $prefix): bool
use IterTools\Stream;
$path = ['var', 'log', 'nginx', 'access.log'];
$boolean = Stream::of($path)->startsWith(['var', 'log']);
// true
$boolean = Stream::of($path)->startsWith(['etc']);
// falseReturns true if the stream starts with the given prefix using type coercion.
- Compares values pairwise; keys are ignored.
- Empty prefix returns true without consuming the stream.
- Scalars are compared non-strictly by value, objects and arrays by serialized value, and
NaNmatchesNaN. - Throws
\InvalidArgumentExceptionif a non-serializable object is reached during comparison.
$stream->startsWithCoercive(iterable $prefix): bool
use IterTools\Stream;
$digits = [1, 2, 3];
$boolean = Stream::of($digits)->startsWithCoercive(['1', '2']);
// true (coercive comparison)Reduces iterable source to the mean average of its items.
$stream->toAverage(): mixed
Returns null if iterable source is empty.
use IterTools\Stream;
$input = [2, 4, 6, 8];
$result = Stream::of($iterable)
->toAverage();
// 5Reduces iterable source to its length.
$stream->toCount(): mixed
use IterTools\Stream;
$input = [10, 20, 30, 40, 50];
$result = Stream::of($iterable)
->toCount();
// 5Reduces iterable source to an array of counts keyed by the value returned from the key function.
$stream->toCountBy(callable $keyFunc): array
The key function must return an int or string; any other return type throws a \TypeError.
use IterTools\Stream;
$words = ['apple', 'pear', 'banana', 'kiwi', 'plum'];
$result = Stream::of($words)
->toCountBy(fn ($word) => \strlen($word));
// [5 => 1, 4 => 3, 6 => 1]Reduces iterable source to its first element.
$stream->toFirst(): mixed
Throws \LengthException if iterable source is empty.
use IterTools\Stream;
$input = [10, 20, 30];
$result = Stream::of($input)
->toFirst();
// 10Reduces iterable source to its first and last elements.
$stream->toFirstAndLast(): array{mixed, mixed}
Throws \LengthException if iterable source is empty.
use IterTools\Stream;
$input = [10, 20, 30];
$result = Stream::of($input)
->toFirstAndLast();
// [10, 30]Reduces iterable source to the first element matching the predicate.
$stream->toFirstMatch(callable $predicate, mixed $default = null): mixed
- Predicate return value is coerced via
(bool)cast. - Short-circuits on the first match.
- Returns
$default(null by default) if no element matches.
use IterTools\Stream;
$numbers = [1, 3, 5, 6, 7, 8];
$result = Stream::of($numbers)
->toFirstMatch(fn (int $n) => $n % 2 === 0);
// 6Reduces iterable source to the zero-based position of the first element matching the predicate.
$stream->toFirstMatchIndex(callable $predicate, mixed $default = null): mixed
- Predicate return value is coerced via
(bool)cast. - Short-circuits on the first match.
- Returns
$default(null by default) if no element matches.
use IterTools\Stream;
$numbers = [10, 20, 30, 40];
$result = Stream::of($numbers)
->toFirstMatchIndex(fn (int $n) => $n > 25);
// 2Reduces iterable source to the source key of the first element matching the predicate.
$stream->toFirstMatchKey(callable $predicate, mixed $default = null): mixed
- Predicate return value is coerced via
(bool)cast. - Short-circuits on the first match.
- Returns
$default(null by default) if no element matches.
use IterTools\Stream;
$users = ['alice' => 12, 'bob' => 17, 'carol' => 22, 'dan' => 30];
$result = Stream::of($users)
->toFirstMatchKey(fn (int $age) => $age >= 18);
// 'carol'Reduces iterable source to its last element.
$stream->toLast(): mixed
Throws \LengthException if iterable source is empty.
use IterTools\Stream;
$input = [10, 20, 30];
$result = Stream::of($input)
->toLast();
// 30Reduces iterable source to the last element matching the predicate.
$stream->toLastMatch(callable $predicate, mixed $default = null): mixed
- Predicate return value is coerced via
(bool)cast. - Consumes the entire iterable.
- Returns
$default(null by default) if no element matches.
use IterTools\Stream;
$numbers = [1, 3, 5, 6, 7, 8, 9];
$result = Stream::of($numbers)
->toLastMatch(fn (int $n) => $n % 2 === 0);
// 8Reduces iterable source to the zero-based position of the last element matching the predicate.
$stream->toLastMatchIndex(callable $predicate, mixed $default = null): mixed
- Predicate return value is coerced via
(bool)cast. - Consumes the entire iterable.
- Returns
$default(null by default) if no element matches.
use IterTools\Stream;
$numbers = [10, 20, 30, 40, 5];
$result = Stream::of($numbers)
->toLastMatchIndex(fn (int $n) => $n > 25);
// 3Reduces iterable source to the source key of the last element matching the predicate.
$stream->toLastMatchKey(callable $predicate, mixed $default = null): mixed
- Predicate return value is coerced via
(bool)cast. - Consumes the entire iterable.
- Returns
$default(null by default) if no element matches.
use IterTools\Stream;
$users = ['alice' => 12, 'bob' => 17, 'carol' => 22, 'dan' => 30];
$result = Stream::of($users)
->toLastMatchKey(fn (int $age) => $age >= 18);
// 'dan'Reduces iterable source to its max value.
$stream->toMax(callable $compareBy = null): mixed
- Optional callable param
$compareBymust return comparable value. - If
$compareByis not provided then items of given collection must be comparable. - Returns null if collection is empty.
use IterTools\Stream;
$input = [1, -1, 2, -2, 3, -3];
$result = Stream::of($iterable)
->toMax();
// 3Reduces iterable source to its median value.
$stream->toMedian(): int|float|null
- For an even number of elements, the median is the mean of the two middle values, computed so that it neither overflows when the two middle values sum beyond
PHP_FLOAT_MAXnor loses precision when their span exceeds the integer range. Two identical middle values return that value, includingINF. - Returns null if iterable source is empty.
use IterTools\Stream;
$grades = [100, 90, 95, 85, 94];
$median = Stream::of($grades)
->toMedian();
// 94Reduces iterable source to its min value.
$stream->toMin(callable $compareBy = null): mixed
- Optional callable param
$compareBymust return comparable value. - If
$compareByis not provided then items of given collection must be comparable. - Returns null if collection is empty.
use IterTools\Stream;
$input = [1, -1, 2, -2, 3, -3];
$result = Stream::of($iterable)
->toMin();
// -3Reduces stream to array of its upper and lower bounds (max and min).
$stream->toMinMax(callable $compareBy = null): array
- Optional callable param
$compareBymust return comparable value. - If
$compareByis not provided then items of given collection must be comparable. - Returns
[null, null]if given collection is empty.
use IterTools\Stream;
$numbers = [1, 2, 7, -1, -2, -3];
[$min, $max] = Stream::of($numbers)
->toMinMax();
// [-3, 7]Reduces iterable source to a list of its modes (the most frequent values).
$stream->toMode(): array
- Returns every value tied for the maximum frequency, in first-seen order.
- Returns an empty array if iterable source is empty.
use IterTools\Stream;
$votes = ['red', 'blue', 'red', 'green', 'blue', 'red'];
$modes = Stream::of($votes)
->toMode();
// ['red']Reduces stream to value at the nth position.
$stream->toNth(int $position): mixed
Returns null if iterable source is empty.
use IterTools\Stream;
$lotrMovies = ['The Fellowship of the Ring', 'The Two Towers', 'The Return of the King'];
$result = Stream::of($lotrMovies)
->toNth(2);
// The Return of the KingReduces iterable source to its sole element.
$stream->toOnly(): mixed
- Throws
\LengthExceptionif the stream is empty or contains more than one element. - Compose with
filter()to assert that exactly one item matches a predicate.
use IterTools\Stream;
$result = Stream::of([1, 2, 3, 4, 5])
->filter(fn (int $n) => $n === 3)
->toOnly();
// 3Reduces iterable source to its value at the given percentile.
$stream->toPercentile(float $percentile): int|float|null
- Uses the R-7 / linear-interpolation method (the NumPy default). Percentile
0is the minimum,100is the maximum. $percentilemust be in the inclusive range[0, 100]; otherwise throws\InvalidArgumentException.- The interpolation does not overflow when the two neighbouring values span more than
PHP_FLOAT_MAX. - Percentile
50returns exactly whattoMedianreturns, for every source. - Returns null if iterable source is empty.
use IterTools\Stream;
$scores = [10, 20, 30, 40, 50];
$p75 = Stream::of($scores)
->toPercentile(75);
// 40Reduces stream to the product of its items.
$stream->toProduct(): mixed
Returns null if iterable source is empty.
use IterTools\Stream;
$input = [1, 2, 3, 4, 5];
$result = Stream::of($iterable)
->toProduct();
// 120Reduces iterable source to its value at the given quantile.
$stream->toQuantile(float $quantile): int|float|null
- Accepts a quantile in the inclusive range
[0, 1](e.g.0.25is the first quartile / 25th percentile). $quantilemust be in the inclusive range[0, 1]; otherwise throws\InvalidArgumentException.- Returns null if iterable source is empty.
use IterTools\Stream;
$scores = [10, 20, 30, 40, 50];
$q3 = Stream::of($scores)
->toQuantile(0.75);
// 40Reduces stream to a random value within it.
$stream->toRandomValue(): mixed
use IterTools\Stream;
$rpsHands = ['rock', 'paper', 'scissors']
$range = Stream::of($numbers)
->map('strtoupper')
->toRandomValue();
// e.g., rockReduces stream to its range (difference between max and min).
$stream->toRange(): int|float
Returns 0 if iterable source is empty.
use IterTools\Stream;
$grades = [100, 90, 80, 85, 95];
$range = Stream::of($numbers)
->toRange();
// 20Reduces iterable source to the standard deviation of its values.
$stream->toStandardDeviation(bool $sample = false): float|null
- Square root of the variance. Population standard deviation by default; pass
$sample = truefor the sample standard deviation (Bessel's correction). - Inherits the single-pass,
O(1)-memory and overflow behavior oftoVariance. - Returns null if iterable source is empty, or for the sample standard deviation of a single value. The null cases take precedence over
NAN, matchingtoVariance.
use IterTools\Stream;
$numbers = [2, 4, 4, 4, 5, 5, 7, 9];
$stddev = Stream::of($numbers)
->toStandardDeviation();
// 2.0Reduces to a string joining all elements.
- Optional separator to insert between items.
- Optional prefix to prepend to the string.
- Optional suffix to append to the string.
$stream->toString(string $separator = '', string $prefix = '', string $suffix = ''): string
use IterTools\Stream;
$words = ['IterTools', 'PHP', 'v1.0'];
$string = Stream::of($words)->toString($words);
// IterToolsPHPv1.0
$string = Stream::of($words)->toString($words, '-');
// IterTools-PHP-v1.0
$string = Stream::of($words)->toString($words, '-', 'Library: ');
// Library: IterTools-PHP-v1.0
$string = Stream::of($words)->toString($words, '-', 'Library: ', '!');
// Library: IterTools-PHP-v1.0!Reduces iterable source to the sum of its items.
$stream->toSum(): mixed
use IterTools\Stream;
$input = [1, 2, 3, 4, 5];
$result = Stream::of($iterable)
->toSum();
// 15Reduces iterable source like array_reduce() function.
But unlike array_reduce(), it works with all iterable types.
$stream->toValue(callable $reducer, mixed $initialValue): mixed
use IterTools\Stream;
$input = [1, 2, 3, 4, 5];
$result = Stream::of($iterable)
->toValue(fn ($carry, $item) => $carry + $item);
// 15Reduces iterable source to the variance of its values.
$stream->toVariance(bool $sample = false): float|null
- Population variance by default; pass
$sample = truefor the sample variance (Bessel's correction). - Uses a scaled online algorithm with a compensated running mean: a single pass in
O(1)memory, so the stream is never materialized. - The result is stable across orderings of the source to within floating-point rounding, but is not bit-reproducible — different orderings may differ in the last ulp.
- Stays finite whenever the variance is representable, even when intermediate quantities are not (the variance of
[-1.4e154, 1.4e154, 0]is~1.31e308, though the variance of its leading pair already exceedsPHP_FLOAT_MAX). - A non-finite value anywhere in the source yields
NAN, since deviations from an infinite mean areINF - INF. A variance that is itself too large to represent is reported asINF, never as a negative number. - Returns null if iterable source is empty, or for the sample variance of a single value. The null cases take precedence over
NAN:toVariance([INF], true)isnull, whiletoVariance([INF])isNAN.
use IterTools\Stream;
$numbers = [1, 2, 3, 4, 5];
$variance = Stream::of($numbers)
->toVariance();
// 2.0Returns an array of stream elements.
$stream->toArray(): array
use IterTools\Stream;
$array = Stream::of([1, 1, 2, 2, 3, 4, 5])
->distinct()
->map(fn ($x) => $x**2)
->toArray();
// [1, 4, 9, 16, 25]Returns a key-value map of stream elements.
$stream->toAssociativeArray(callable $keyFunc, callable $valueFunc): array
use IterTools\Stream;
$keyFunc
$array = Stream::of(['message 1', 'message 2', 'message 3'])
->map('strtoupper')
->toAssociativeArray(
fn ($s) => \md5($s),
fn ($s) => $s
);
// [3b3f2272b3b904d342b2d0df2bf31ed4 => MESSAGE 1, 43638d919cfb8ea31979880f1a2bb146 => MESSAGE 2, ... ]Partitions the stream into two lists based on a predicate.
Returns a two-element list array: [truthyValues, falsyValues]. Both output arrays are reindexed (list arrays); source keys are discarded. Predicate return value is coerced via (bool) cast.
$stream->toPartition(callable $predicate): array
use IterTools\Stream;
[$evens, $odds] = Stream::of([1, 2, 3, 4, 5, 6])
->toPartition(fn (int $n): bool => $n % 2 === 0);
// $evens: [2, 4, 6]
// $odds: [1, 3, 5]Since both halves are returned together, toPartition pairs naturally with operations that consume two lists. For example, tournament seeding — partition seeds into top and bottom halves, then pair top vs reversed bottom to build matchups:
use IterTools\Stream;
[$topHalf, $bottomHalf] = Stream::of([1, 2, 3, 4, 5, 6, 7, 8])
->toPartition(fn (int $seed): bool => $seed <= 4);
$matchups = Stream::of($topHalf)
->zipWith(array_reverse($bottomHalf))
->toArray();
// [[1, 8], [2, 7], [3, 6], [4, 5]]Or, treating both halves as rows of a single stream and transposing them with zip:
use IterTools\Stream;
[$topHalf, $bottomHalf] = Stream::of([1, 2, 3, 4, 5, 6, 7, 8])
->toPartition(fn (int $seed): bool => $seed <= 4);
$matchups = Stream::of([$topHalf, array_reverse($bottomHalf)])
->zip()
->toArray();
// [[1, 8], [2, 7], [3, 6], [4, 5]]Return several independent (duplicated) streams.
$stream->tee(int $count): array
use IterTools\Transform;
$daysOfWeek = ['Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat', 'Sun'];
$count = 3;
[$week1Stream, $week2Stream, $week3Stream] = Stream::of($daysOfWeek)
->tee($count);
// Each $weekStream contains ['Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat', 'Sun']Perform an action via a callable on each item in the stream.
$stream->callForEach(callable $function): void
use IterTools\Stream;
$languages = ['PHP', 'Python', 'Java', 'Go'];
$mascots = ['elephant', 'snake', 'bean', 'gopher'];
$zipPrinter = fn ($zipped) => print("{$zipped[0]}'s mascot: {$zipped[1]}");
Stream::of($languages)
->zipWith($mascots)
->callForEach($zipPrinter);
// PHP's mascot: elephant
// Python's mascot: snake
// ...Drains the stream, discarding values.
Useful for forcing evaluation of a lazy pipeline whose only purpose is its side effects (for example, a side-effectful map()).
$stream->consume(): void
use IterTools\Stream;
$log = [];
$pipeline = Stream::of([1, 2, 3])
->map(function (int $n) use (&$log): int {
$log[] = $n;
return $n * 2;
});
// $log === [] (map is lazy — nothing has run yet)
$pipeline->consume();
// $log === [1, 2, 3]Prints each item in the stream.
- Items must be printable.
$stream->print(string $separator = '', string $prefix = '', string $suffix = ''): void
use IterTools\Stream;
$words = ['IterTools', 'PHP', 'v1.0'];
Stream::of($words)->print(); // IterToolsPHPv1.0
Stream::of($words)->print('-'); // IterTools-PHP-v1.0
Stream::of($words)->print('-', 'Library: '); // Library: IterTools-PHP-v1.0
Stream::of($words)->print('-', 'Library: ', '!'); // Library: IterTools-PHP-v1.0!Prints each item in the stream on its own line.
- Items must be printable.
$stream->println(): void
use IterTools\Stream;
$words = ['IterTools', 'PHP', 'v1.0'];
Stream::of($words)->printLn();
// IterTools
// PHP
// v1.0Write the contents of the stream to a CSV file.
$stream->toCsvFile(resource $fileHandle, array $header = null, string 'separator = ',', string $enclosure = '"', string $escape = '\\'): void
use IterTools\Stream;
$starWarsMovies = [
['Star Wars: Episode IV – A New Hope', 'IV', 1977],
['Star Wars: Episode V – The Empire Strikes Back', 'V', 1980],
['Star Wars: Episode VI – Return of the Jedi', 'VI', 1983],
];
$header = ['title', 'episode', 'year'];
Stream::of($data)
->toCsvFile($fh, $header);
// title,episode,year
// "Star Wars: Episode IV – A New Hope",IV,1977
// "Star Wars: Episode V – The Empire Strikes Back",V,1980
// "Star Wars: Episode VI – Return of the Jedi",VI,1983Write the contents of the stream to a file.
$stream->toFile(resource $fileHandle, string $newLineSeparator = \PHP_EOL, string $header = null, string $footer = null): void
use IterTools\Stream;
$data = ['item1', 'item2', 'item3'];
$header = '<ul>';
$footer = '</ul>';
Stream::of($data)
->map(fn ($item) => " <li>$item</li>")
->toFile($fh, \PHP_EOL, $header, $footer);
// <ul>
// <li>item1</li>
// <li>item2</li>
// <li>item3</li>
// </ul>Peek at each element between other Stream operations to do some action without modifying the stream.
Lazy: the callback fires once per element, at the moment a downstream operation pulls that element through the stream. Elements that downstream never consumes are never peeked.
$stream->peek(callable $callback): Stream
use IterTools\Stream;
$logger = new SimpleLog\Logger('/tmp/log.txt', 'iterTools');
Stream::of(['some', 'items'])
->map('strtoupper')
->peek(fn ($x) => $logger->info($x))
->foreach($someComplexCallable);Peek at the entire stream between other Stream operations to do some action without modifying the stream.
Operates on the stream as a whole, and is eager: unlike the per-element peek, the callback is invoked once, at the time peekStream() is called, before any downstream operation runs. It receives its own stream over a copy of the source; consuming that stream inside the callback buffers the upstream so the main stream can replay it afterwards.
$stream->peekStream(callable $callback): Stream
use IterTools\Stream;
$logger = new SimpleLog\Logger('/tmp/log.txt', 'iterTools');
Stream::of(['some', 'items'])
->map('strtoupper')
->peekStream(fn ($stream) => $logger->info($stream))
->foreach($someComplexCallable);Peek at each element between other Stream operations to print each item without modifying the stream.
Eager: delegates to peekStream, so the entire upstream is printed and buffered at the time peekPrint() is called, before any downstream operation runs. The per-element laziness of peek does not apply here.
$stream->peekPrint(string $separator = '', string $prefix = '', string $suffix = ''): Stream
use IterTools\Stream;
Stream::of(['some', 'items'])
->map('strtoupper')
->peekPrint()
->foreach($someComplexCallable);Peek at each element between other Stream operations to print_r each item without modifying the stream.
Eager: delegates to peekStream, so the entire upstream is printed and buffered at the time peekPrintR() is called, before any downstream operation runs. The per-element laziness of peek does not apply here.
$stream->peekPrintR(): Stream
use IterTools\Stream;
Stream::of(['some', 'items'])
->map('strtoupper')
->peekPrintR()
->foreach($someComplexCallable);print_r each item in the stream.
$stream->printR(): void
use IterTools\Stream;
$items = [$string, $array, $object];
Stream::of($words)->printR();
// print_r outputvar_dump each item in the stream.
$stream->varDump(): void
use IterTools\Stream;
$items = [$string, $array, $object];
Stream::of($words)->varDump();
// var_dump output