diff --git a/composer.json b/composer.json index 2ad997c..753c43b 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "./src/Predicates/index.php", "./src/Collections/Internal/functions.php", "./src/Collections/Internal/containers.php", + "./src/Collections/Internal/queries.php", "./src/Collections/transforms.php", "./src/Collections/collectors.php", "./src/Collections/queries.php" diff --git a/src/Collections/Internal/queries.php b/src/Collections/Internal/queries.php new file mode 100644 index 0000000..0b07fd8 --- /dev/null +++ b/src/Collections/Internal/queries.php @@ -0,0 +1,97 @@ + + * + * Full license available in separate LICENSE file + */ + +namespace Kadet\Functional\Collections\Internal; + +use Kadet\Functional\Exception\EmptyCollectionException; + +/** + * Yields [key, value] pairs of $data from the end, without copying it where the container allows. + * + * @internal + * + * @param iterable $data + * + * @return \Generator + */ +function reversedEntries(iterable $data): \Generator +{ + if (is_array($data)) { + for (end($data); ($key = key($data)) !== null; prev($data)) { + yield [$key, current($data)]; + } + } elseif ($data instanceof \Ds\Seq || $data instanceof \Ds\Set) { + for ($index = \count($data) - 1; $index >= 0; $index--) { + yield [$index, $data->get($index)]; + } + } elseif ($data instanceof \Ds\Map) { + foreach ($data->pairs()->reversed() as $pair) { + yield [$pair->key, $pair->value]; + } + } else { + $entries = []; + foreach ($data as $key => $value) { + $entries[] = [$key, $value]; + } + + yield from array_reverse($entries); + } +} + +/** + * Shared implementation of {@see first()} and {@see find()}, so that the exception names the called function. + * + * @internal + * + * @return \Closure(iterable): mixed + */ +function firstOf(string $name, ?callable $predicate, bool $required): \Closure +{ + $predicate = $predicate === null ? null : callback($predicate); + + return static function (iterable $data) use ($name, $predicate, $required) { + foreach ($data as $key => $value) { + if ($predicate === null || $predicate($value, $key)) { + return $value; + } + } + + if ($required) { + throw EmptyCollectionException::noMatch($name, $predicate !== null); + } + + return null; + }; +} + +/** + * Shared implementation of {@see min()} (`$direction` -1) and {@see max()} (1). + * + * @internal + * + * @return \Closure(iterable): mixed + */ +function extremum(int $direction, ?callable $selector): \Closure +{ + $selector = $selector === null ? static fn ($value) => $value : callback($selector); + + return static function (iterable $data) use ($direction, $selector) { + $best = null; + $first = true; + foreach ($data as $key => $value) { + $value = $selector($value, $key); + if ($first || ($value <=> $best) === $direction) { + $best = $value; + $first = false; + } + } + + return $best; + }; +} diff --git a/src/Collections/queries.php b/src/Collections/queries.php index 5a100fc..1979936 100644 --- a/src/Collections/queries.php +++ b/src/Collections/queries.php @@ -22,41 +22,11 @@ namespace Kadet\Functional\Collections; use Kadet\Functional\Exception\EmptyCollectionException; use function Kadet\Functional\Collections\Internal\callback; +use function Kadet\Functional\Collections\Internal\extremum; +use function Kadet\Functional\Collections\Internal\firstOf; +use function Kadet\Functional\Collections\Internal\reversedEntries; use function Kadet\Functional\compile; -/** - * Yields [key, value] pairs of $data from the end, without copying it where the container allows. - * - * @internal - * - * @param iterable $data - * - * @return \Generator - */ -function reversedEntries(iterable $data): \Generator -{ - if (is_array($data)) { - for (end($data); ($key = key($data)) !== null; prev($data)) { - yield [$key, current($data)]; - } - } elseif ($data instanceof \Ds\Seq || $data instanceof \Ds\Set) { - for ($index = \count($data) - 1; $index >= 0; $index--) { - yield [$index, $data->get($index)]; - } - } elseif ($data instanceof \Ds\Map) { - foreach ($data->pairs()->reversed() as $pair) { - yield [$pair->key, $pair->value]; - } - } else { - $entries = []; - foreach ($data as $key => $value) { - $entries[] = [$key, $value]; - } - - yield from array_reverse($entries); - } -} - /** * Returns the first value for which `$predicate($value, $key)` is truthy, or the first value without a predicate. * @@ -145,32 +115,6 @@ function find(callable $predicate, bool $required = false): \Closure return firstOf('find', $predicate, $required); } -/** - * Shared implementation of {@see first()} and {@see find()}, so that the exception names the called function. - * - * @internal - * - * @return \Closure(iterable): mixed - */ -function firstOf(string $name, ?callable $predicate, bool $required): \Closure -{ - $predicate = $predicate === null ? null : callback($predicate); - - return static function (iterable $data) use ($name, $predicate, $required) { - foreach ($data as $key => $value) { - if ($predicate === null || $predicate($value, $key)) { - return $value; - } - } - - if ($required) { - throw EmptyCollectionException::noMatch($name, $predicate !== null); - } - - return null; - }; -} - /** * Whether the collection holds `$value`, compared strictly (`Ds\Key::equals` when `$value` implements `Ds\Key`). * @@ -330,32 +274,6 @@ function max(?callable $selector = null): \Closure return extremum(1, $selector); } -/** - * Shared implementation of {@see min()} (`$direction` -1) and {@see max()} (1). - * - * @internal - * - * @return \Closure(iterable): mixed - */ -function extremum(int $direction, ?callable $selector): \Closure -{ - $selector = $selector === null ? static fn ($value) => $value : callback($selector); - - return static function (iterable $data) use ($direction, $selector) { - $best = null; - $first = true; - foreach ($data as $key => $value) { - $value = $selector($value, $key); - if ($first || ($value <=> $best) === $direction) { - $best = $value; - $first = false; - } - } - - return $best; - }; -} - /** * Folds the collection into a single value with `$fn($carry, $value, $key)`, starting from `$initial`. *