Работа с итераторами
Nette\Utils\Iterables – статический класс с функциями для работы с итераторами. Его аналог для массивов – Nette\Utils\Arrays.
Установка:
composer require nette/utils
Во всех примерах предполагается, что создан такой псевдоним:
use Nette\Utils\Iterables;
contains (iterable $iterable, $value): bool
Ищет заданное значение в итераторе. Для проверки совпадения
использует строгое сравнение (===). Возвращает true, если
значение найдено, иначе false.
Iterables::contains(new ArrayIterator([1, 2, 3]), 1); // true
Iterables::contains(new ArrayIterator([1, 2, 3]), '1'); // false
Этот метод полезен, когда нужно быстро определить, есть ли в итераторе определённое значение, не обходя все элементы вручную.
containsKey (iterable $iterable, $key): bool
Ищет заданный ключ в итераторе. Для проверки совпадения использует
строгое сравнение (===). Возвращает true, если ключ найден,
иначе false.
Iterables::containsKey(new ArrayIterator([1, 2, 3]), 0); // true
Iterables::containsKey(new ArrayIterator([1, 2, 3]), 4); // false
every (iterable $iterable, callable $predicate): bool
Проверяет, удовлетворяют ли все элементы итератора условию,
заданному в $predicate. Callback $predicate имеет сигнатуру
function ($value, $key, iterable $iterable): bool и должен вернуть true для
каждого элемента, чтобы метод every() вернул true.
$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]);
$isBelowThreshold = fn($value) => $value < 40;
$res = Iterables::every($iterator, $isBelowThreshold); // true
Этот метод полезен для проверки того, что все элементы коллекции отвечают определённому условию, например что все числа меньше заданного значения.
filter (iterable $iterable, callable $predicate): Generator
Создаёт новый итератор, содержащий только те элементы исходного
итератора, которые удовлетворяют условию, заданному в $predicate.
Callback $predicate имеет сигнатуру function ($value, $key, iterable $iterable): bool и
должен вернуть true для элементов, которые нужно сохранить.
$iterator = new ArrayIterator([1, 2, 3]);
$iterator = Iterables::filter($iterator, fn($v) => $v < 3);
// 1, 2
Метод использует генератор, то есть фильтрация происходит постепенно, по мере обхода результата. Это экономит память и позволяет обрабатывать очень большие коллекции. Если вы не обойдёте все элементы получившегося итератора, вы сэкономите и вычислительные ресурсы, потому что не все элементы исходного итератора будут обработаны.
first (iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed
Возвращает первый элемент итератора. Если задан $predicate,
возвращает первый элемент, удовлетворяющий условию. Callback $predicate
имеет сигнатуру function ($value, $key, iterable $iterable): bool. Если подходящий
элемент не найден, вызывается callback $else (если он задан), и
возвращается его результат. Если $else не задан, возвращается
null.
Iterables::first(new ArrayIterator([1, 2, 3])); // 1
Iterables::first(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 3
Iterables::first(new ArrayIterator([])); // null
Iterables::first(new ArrayIterator([]), else: fn() => false); // false
Этот метод полезен, когда нужно быстро получить первый элемент коллекции или первый элемент, удовлетворяющий определённому условию, не обходя всю коллекцию вручную.
firstKey (iterable $iterable, ?callable $predicate=null, ?callable $else=null): mixed
Возвращает ключ первого элемента итератора. Если задан $predicate,
возвращает ключ первого элемента, удовлетворяющего условию. Callback
$predicate имеет сигнатуру function ($value, $key, iterable $iterable): bool. Если
подходящий элемент не найден, вызывается callback $else (если он
задан), и возвращается его результат. Если $else не задан,
возвращается null.
Iterables::firstKey(new ArrayIterator([1, 2, 3])); // 0
Iterables::firstKey(new ArrayIterator([1, 2, 3]), fn($v) => $v > 2); // 2
Iterables::firstKey(new ArrayIterator(['a' => 1, 'b' => 2])); // 'a'
Iterables::firstKey(new ArrayIterator([])); // null
map (iterable $iterable, callable $transformer): Generator
Создаёт новый итератор, применяя callback $transformer к каждому
элементу исходного итератора. Callback $transformer имеет сигнатуру
function ($value, $key, iterable $iterable): mixed, и его возвращаемое значение
используется как новое значение элемента.
$iterator = new ArrayIterator([1, 2, 3]);
$iterator = Iterables::map($iterator, fn($v) => $v * 2);
// 2, 4, 6
Метод использует генератор, то есть преобразование происходит постепенно, по мере обхода результата. Это экономит память и позволяет обрабатывать очень большие коллекции. Если вы не обойдёте все элементы получившегося итератора, вы сэкономите и вычислительные ресурсы, потому что не все элементы исходного итератора будут обработаны.
mapWithKeys (iterable $iterable, callable $transformer): Generator
Создаёт новый итератор, преобразуя значения и ключи исходного
итератора. Callback $transformer имеет сигнатуру
function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}. Если $transformer
возвращает null, элемент пропускается. Для сохранённых элементов
первый элемент возвращённого массива используется как новый ключ, а
второй – как новое значение.
$iterator = new ArrayIterator(['a' => 1, 'b' => 2]);
$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null);
// [4 => 'B']
Как и map(), этот метод использует генератор ради постепенной
обработки и экономии памяти. Это позволяет работать с большими
коллекциями и экономить вычислительные ресурсы при частичном обходе
результата.
memoize (iterable $iterable): IteratorAggregate
Создаёт обёртку вокруг итератора, которая кеширует его ключи и значения во время обхода. Это позволяет обходить данные повторно, не обращаясь снова к исходному источнику данных.
$iterator = /* ... */; // данные, которые нельзя обойти несколько раз
$memoized = Iterables::memoize($iterator);
// Теперь вы можете обходить $memoized несколько раз без потери данных
Этот метод полезен в ситуациях, когда вам нужно обойти один и тот же набор данных несколько раз, но исходный итератор не допускает повторного обхода или повторный проход был бы дорогим (например, чтение данных из базы данных или файла).
repeatable (callable $factory): IteratorAggregate
Позволяет повторно обходить объекты, которые иначе этого не
поддерживают, обычно генераторы PHP. Метод
repeatable() решает эту задачу изящно: вместо самого итератора вы
передаёте функцию, которая его создаёт. Эта фабрика затем
автоматически вызывается при каждом цикле обхода.
// Обычный генератор, который нельзя обойти дважды
$generator = function () {
yield 'A';
yield 'B';
};
$iterator = Iterables::repeatable($generator);
foreach ($iterator as $v) echo $v; // Выводит: AB
foreach ($iterator as $v) echo $v; // Выводит: AB (генератор запустился снова)
Этот метод – альтернатива memoize() в ситуациях, когда вы
работаете с большими объёмами данных, потому что repeatable() не
кеширует данные, а порождает их заново при каждом обходе.
some (iterable $iterable, callable $predicate): bool
Проверяет, удовлетворяет ли хотя бы один элемент итератора условию,
заданному в $predicate. Callback $predicate имеет сигнатуру
function ($value, $key, iterable $iterable): bool и должен вернуть true хотя бы
для одного элемента, чтобы метод some() вернул true.
$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]);
$isEven = fn($value) => $value % 2 === 0;
$res = Iterables::some($iterator, $isEven); // true
Этот метод полезен для быстрой проверки того, отвечает ли хотя бы один элемент коллекции определённому условию, например есть ли в коллекции хотя бы одно чётное число.
См. every().
toIterator (iterable $iterable): Iterator
Преобразует любой итерируемый объект (массив, Traversable) в Iterator. Если на входе уже Iterator, он возвращается без изменений.
$array = [1, 2, 3];
$iterator = Iterables::toIterator($array);
// Теперь у вас Iterator вместо массива
Этот метод полезен, когда нужно гарантировать наличие Iterator независимо от типа входных данных. Это может пригодиться при создании функций, работающих с разными типами итерируемых данных.