Tools for reducing iterable collections to single values.
Reduces to the mean average.
Returns null if collection is empty.
Reduce::toAverage(iterable $data): float
use IterTools\Reduce;
$grades = [100, 90, 95, 85, 94];
$finalGrade = Reduce::toAverage($numbers);
// 92.8Reduces iterable to its length.
Reduce::toCount(iterable $data): int
use IterTools\Reduce;
$someIterable = ImportantThing::getCollectionAsIterable();
$length = Reduce::toCount($someIterable);
// 3Reduces iterable to an array of counts keyed by the value returned from the key function.
Reduce::toCountBy(iterable $data, callable $keyFunc): array
The key function must return an int or string (the only valid array-key types). Any other
return type throws a \TypeError naming the offending type.
Note: PHP arrays coerce numeric-string keys to int — a key function that returns the string
"1"and one that returns the int1collapse into a single int key1with the combined count.
use IterTools\Reduce;
$words = ['apple', 'pear', 'banana', 'kiwi', 'plum'];
$counts = Reduce::toCountBy($words, fn ($word) => \strlen($word));
// [5 => 1, 4 => 3, 6 => 1]Reduces iterable to its first element.
Reduce::toFirst(iterable $data): mixed
Throws \LengthException if collection is empty.
use IterTools\Reduce;
$medals = ['gold', 'silver', 'bronze'];
$first = Reduce::toFirst($medals);
// goldReduces iterable to its first and last elements.
Reduce::toFirstAndLast(iterable $data): array{mixed, mixed}
Throws \LengthException if collection is empty.
use IterTools\Reduce;
$weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
$firstAndLast = Reduce::toFirstAndLast($weekdays);
// [Monday, Friday]Reduces iterable to its first element matching the predicate.
Reduce::toFirstMatch(iterable $data, callable $predicate, mixed $default = null): mixed
- Predicate return value is coerced via
(bool)cast. - Short-circuits on the first match — the iterable is not fully consumed.
- Returns
$default(null by default) if no element matches.
use IterTools\Reduce;
$numbers = [1, 3, 5, 6, 7, 8];
$firstEven = Reduce::toFirstMatch($numbers, fn (int $n) => $n % 2 === 0);
// 6
$firstNegative = Reduce::toFirstMatch($numbers, fn (int $n) => $n < 0, -1);
// -1Reduces iterable to the zero-based position of the first element matching the predicate.
Reduce::toFirstMatchIndex(iterable $data, callable $predicate, mixed $default = null): mixed
- Predicate return value is coerced via
(bool)cast. - Short-circuits on the first match — the iterable is not fully consumed.
- Returns
$default(null by default) if no element matches. - Position is always counted from the start of iteration, regardless of source keys.
use IterTools\Reduce;
$numbers = [10, 20, 30, 40];
$firstOver25Index = Reduce::toFirstMatchIndex($numbers, fn (int $n) => $n > 25);
// 2use IterTools\Reduce;
// Early-exit search: a generator that would throw on the item after the match
// is never advanced past the matching position.
$ids = (function (): \Generator {
yield 1;
yield 2;
yield 3;
throw new \RuntimeException('iterator advanced past match');
})();
$index = Reduce::toFirstMatchIndex($ids, fn (int $n) => $n === 2);
// 1Reduces iterable to the source key of the first element matching the predicate.
Reduce::toFirstMatchKey(iterable $data, callable $predicate, mixed $default = null): mixed
- Predicate return value is coerced via
(bool)cast. - Short-circuits on the first match — the iterable is not fully consumed.
- Returns
$default(null by default) if no element matches. - Preserves the source key (string for associative input, int for list-shape input).
use IterTools\Reduce;
$users = ['alice' => 12, 'bob' => 17, 'carol' => 22, 'dan' => 30];
$firstAdultName = Reduce::toFirstMatchKey($users, fn (int $age) => $age >= 18);
// 'carol'use IterTools\Reduce;
$prices = ['usd' => 9.99, 'eur' => 8.49, 'jpy' => 1499.0];
$firstExpensiveCurrency = Reduce::toFirstMatchKey(
$prices,
fn (float $p) => $p > 1000,
'none'
);
// 'jpy'Reduces iterable to its last element.
Reduce::toLast(iterable $data): mixed
Throws \LengthException if collection is empty.
use IterTools\Reduce;
$gnomesThreePhasePlan = ['Collect underpants', '?', 'Profit'];
$lastPhase = Reduce::toLast($gnomesThreePhasePlan);
// ProfitReduces iterable to the last element matching the predicate.
Reduce::toLastMatch(iterable $data, callable $predicate, mixed $default = null): mixed
- Predicate return value is coerced via
(bool)cast. - Consumes the entire iterable (no short-circuit possible).
- Returns
$default(null by default) if no element matches.
use IterTools\Reduce;
$numbers = [1, 3, 5, 6, 7, 8, 9];
$lastEven = Reduce::toLastMatch($numbers, fn (int $n) => $n % 2 === 0);
// 8
$lastNegative = Reduce::toLastMatch($numbers, fn (int $n) => $n < 0, 'none');
// 'none'Reduces iterable to the zero-based position of the last element matching the predicate.
Reduce::toLastMatchIndex(iterable $data, 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. - For associative input, returns the zero-based position rather than the source key.
use IterTools\Reduce;
$numbers = [10, 20, 30, 40, 5];
$lastOver25Index = Reduce::toLastMatchIndex($numbers, fn (int $n) => $n > 25);
// 3Reduces iterable to the source key of the last element matching the predicate.
Reduce::toLastMatchKey(iterable $data, 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. - Preserves the source key (string for associative input, int for list-shape input).
use IterTools\Reduce;
$users = ['alice' => 12, 'bob' => 17, 'carol' => 22, 'dan' => 30];
$lastAdultName = Reduce::toLastMatchKey($users, fn (int $age) => $age >= 18);
// 'dan'Reduces to the max value.
Reduce::toMax(iterable $data, callable $compareBy = null): mixed|null
- 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\Reduce;
$numbers = [5, 3, 1, 2, 4];
$result = Reduce::toMax($numbers);
// 5
$movieRatings = [
[
'title' => 'Star Wars: Episode IV - A New Hope',
'rating' => 4.6
],
[
'title' => 'Star Wars: Episode V - The Empire Strikes Back',
'rating' => 4.8
],
[
'title' => 'Star Wars: Episode VI - Return of the Jedi',
'rating' => 4.6
],
];
$compareBy = fn ($movie) => $movie['rating'];
$highestRatedMovie = Reduce::toMax($movieRatings, $compareBy);
// [
// 'title' => 'Star Wars: Episode V - The Empire Strikes Back',
// 'rating' => 4.8
// ];Reduces to the median value.
Reduce::toMedian(iterable $data): 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 (toMedian([PHP_INT_MIN, PHP_INT_MAX])is-0.5, not0.0). Two identical middle values return that value, includingINF. - Returns null if collection is empty.
use IterTools\Reduce;
$grades = [100, 90, 95, 85, 94];
$median = Reduce::toMedian($grades);
// 94Reduces to the min value.
Reduce::toMin(iterable $data, callable $compareBy = null): mixed|null
- 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\Reduce;
$numbers = [5, 3, 1, 2, 4];
$result = Reduce::toMin($numbers);
// 1
$movieRatings = [
[
'title' => 'The Matrix',
'rating' => 4.7
],
[
'title' => 'The Matrix Reloaded',
'rating' => 4.3
],
[
'title' => 'The Matrix Revolutions',
'rating' => 3.9
],
[
'title' => 'The Matrix Resurrections',
'rating' => 2.5
],
];
$compareBy = fn ($movie) => $movie['rating'];
$lowestRatedMovie = Reduce::toMin($movieRatings, $compareBy);
// [
// 'title' => 'The Matrix Resurrections',
// 'rating' => 2.5
// ]Reduces to array of its upper and lower bounds (max and min).
Reduce::toMinMax(iterable $numbers, 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\Reduce;
$numbers = [1, 2, 7, -1, -2, -3];
[$min, $max] = Reduce::toMinMax($numbers);
// [-3, 7]
$reportCard = [
[
'subject' => 'history',
'grade' => 90
],
[
'subject' => 'math',
'grade' => 98
],
[
'subject' => 'science',
'grade' => 92
],
[
'subject' => 'english',
'grade' => 85
],
[
'subject' => 'programming',
'grade' => 100
],
];
$compareBy = fn ($class) => $class['grade'];
$bestAndWorstSubject = Reduce::toMinMax($reportCard, $compareBy);
// [
// [
// 'subject' => 'english',
// 'grade' => 85
// ],
// [
// 'subject' => 'programming',
// 'grade' => 100
// ],
// ]Reduces to a list of its modes (the most frequent values).
Reduce::toMode(iterable $data): array
- Returns every value tied for the maximum frequency, in first-seen order (an all-unique input returns all of its values).
- Multimodal inputs return multiple modes.
- Values are compared strictly, so
1,1.0, and'1'count as distinct. - Returns an empty array if collection is empty.
use IterTools\Reduce;
$votes = ['red', 'blue', 'red', 'green', 'blue', 'red'];
$modes = Reduce::toMode($votes);
// ['red']Reduces to value at the nth position.
Reduce::toNth(iterable $data, int $position): mixed
use IterTools\Reduce;
$lotrMovies = ['The Fellowship of the Ring', 'The Two Towers', 'The Return of the King'];
$rotk = Reduce::toNth($lotrMovies, 2);
// 20Reduces iterable to its sole element.
Reduce::toOnly(iterable $data): mixed
- Throws
\LengthExceptionif the iterable is empty or contains more than one element. - For associative single-element input, returns the value (not the key).
- Compose with
Stream::filter()->toOnly()to assert that exactly one item matches a predicate.
use IterTools\Reduce;
$config = ['admin' => 'jane'];
$onlyAdmin = Reduce::toOnly($config);
// 'jane'use IterTools\Reduce;
Reduce::toOnly([]); // throws \LengthException
Reduce::toOnly([1, 2, 3]); // throws \LengthExceptionReduces to its value at the given percentile.
Reduce::toPercentile(iterable $data, 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 input. - Returns null if collection is empty.
use IterTools\Reduce;
$scores = [10, 20, 30, 40, 50];
$p75 = Reduce::toPercentile($scores, 75);
// 40Reduces to the product of its elements.
Returns null if collection is empty.
Reduce::toProduct(iterable $data): number|null
use IterTools\Reduce;
$primeFactors = [5, 2, 2];
$number = Reduce::toProduct($primeFactors);
// 20Reduces to its value at the given quantile.
Reduce::toQuantile(iterable $data, float $quantile): int|float|null
- Thin wrapper over
toPercentilethat 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 collection is empty.
use IterTools\Reduce;
$scores = [10, 20, 30, 40, 50];
$q3 = Reduce::toQuantile($scores, 0.75);
// 40Reduces given collection to a random value within it.
Reduce::toRandomValue(iterable $data): mixed
use IterTools\Reduce;
$sfWakeupOptions = ['mid', 'low', 'overhead', 'throw', 'meaty'];
$wakeupOption = Reduce::toRandomValue($sfWakeupOptions);
// e.g., throwReduces given collection to its range (difference between max and min).
Reduce::toRange(iterable $numbers): int|float
Returns 0 if iterable source is empty.
use IterTools\Reduce;
$grades = [100, 90, 80, 85, 95];
$range = Reduce::toRange($numbers);
// 20Reduces to the standard deviation of its values.
Reduce::toStandardDeviation(iterable $data, 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 collection is empty, or for the sample standard deviation of a single value. The population standard deviation of a single value is
0.0. - The null cases take precedence over
NAN, matchingtoVariance:toStandardDeviation([INF], true)isnull, whiletoStandardDeviation([INF])isNAN.
use IterTools\Reduce;
$numbers = [2, 4, 4, 4, 5, 5, 7, 9];
$populationStdDev = Reduce::toStandardDeviation($numbers);
// 2.0
$sampleStdDev = Reduce::toStandardDeviation($numbers, true);
// 2.138...Reduces 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.
Reduce::toString(iterable $data, string $separator = '', string $prefix = '', string $suffix = ''): string
use IterTools\Reduce;
$words = ['IterTools', 'PHP', 'v1.0'];
$string = Reduce::toString($words);
// IterToolsPHPv1.0
$string = Reduce::toString($words, '-');
// IterTools-PHP-v1.0
$string = Reduce::toString($words, '-', 'Library: ');
// Library: IterTools-PHP-v1.0
$string = Reduce::toString($words, '-', 'Library: ', '!');
// Library: IterTools-PHP-v1.0!Reduces to the sum of its elements.
Reduce::toSum(iterable $data): number
use IterTools\Reduce;
$parts = [10, 20, 30];
$sum = Reduce::toSum($parts);
// 60Reduce elements to a single value using reducer function.
Reduce::toValue(iterable $data, callable $reducer, mixed $initialValue): mixed
use IterTools\Reduce;
$input = [1, 2, 3, 4, 5];
$sum = fn ($carry, $item) => $carry + $item;
$result = Reduce::toValue($input, $sum, 0);
// 15Reduces to the variance of its values.
Reduce::toVariance(iterable $data, bool $sample = false): float|null
- Population variance by default; pass
$sample = truefor the sample variance (Bessel's correction — divides byN - 1). - Uses a scaled online algorithm with a compensated running mean: a single pass in
O(1)memory, so the collection is never materialized and large lazy iterables are safe. - The result is stable across orderings of the input to within floating-point rounding. It is not bit-reproducible — floating-point addition is not associative, so different orderings may differ in the last ulp. The compensated mean does remove the gross order-dependence that an uncompensated one has for a large offset with a small spread (e.g.
1e16, 1e16 + 2, 1e16 + 4, where the answer would otherwise vary by 50% with ordering). - 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 (1.96e308) already exceedsPHP_FLOAT_MAX. - A non-finite value anywhere in the input 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 collection is empty, or for the sample variance of a single value (
N - 1 = 0is undefined). The population variance of a single value is0.0. - The null cases take precedence over
NAN:toVariance([INF], true)isnull, notNAN, because with a single observation there is no sample variance to compute at all, whatever that observation happens to be. The population variance of one value is defined, sotoVariance([INF])isNAN.
use IterTools\Reduce;
$numbers = [1, 2, 3, 4, 5];
$populationVariance = Reduce::toVariance($numbers);
// 2.0
$sampleVariance = Reduce::toVariance($numbers, true);
// 2.5Drains the given iterable, discarding values.
Reduce::consume(iterable $data): void
- Useful for forcing evaluation of a lazy pipeline whose only purpose is its side effects.
- Returns nothing.
use IterTools\Reduce;
use IterTools\Single;
$log = [];
$pipeline = Single::map([1, 2, 3], function (int $n) use (&$log): int {
$log[] = $n;
return $n * 2;
});
// $log === [] (Single::map is lazy — nothing has run yet)
Reduce::consume($pipeline);
// $log === [1, 2, 3]