Praca z iteratorami

Nette\Utils\Iterables to klasa statyczna z funkcjami do pracy z iteratorami. Jej odpowiednikiem dla tablic jest Nette\Utils\Arrays.

Instalacja:

composer require nette/utils

Wszystkie przykłady zakładają, że utworzony jest następujący alias:

use Nette\Utils\Iterables;

contains (iterable $iterable, $value)bool

Szuka podanej wartości w iteratorze. Do sprawdzenia zgodności używa porównania ścisłego (===). Zwraca true, jeśli wartość zostanie znaleziona, w przeciwnym razie false.

Iterables::contains(new ArrayIterator([1, 2, 3]), 1);    // true
Iterables::contains(new ArrayIterator([1, 2, 3]), '1');  // false

Ta metoda przydaje się, gdy potrzebujesz szybko ustalić, czy w iteratorze istnieje konkretna wartość, bez ręcznego przechodzenia przez wszystkie elementy.

containsKey (iterable $iterable, $key)bool

Szuka podanego klucza w iteratorze. Do sprawdzenia zgodności używa porównania ścisłego (===). Zwraca true, jeśli klucz zostanie znaleziony, w przeciwnym razie 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

Sprawdza, czy wszystkie elementy iteratora spełniają warunek zdefiniowany w $predicate. Callback $predicate ma sygnaturę function ($value, $key, iterable $iterable): bool i musi zwrócić true dla każdego elementu, aby metoda every() zwróciła true.

$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]);
$isBelowThreshold = fn($value) => $value < 40;
$res = Iterables::every($iterator, $isBelowThreshold); // true

Ta metoda przydaje się do sprawdzenia, czy wszystkie elementy kolekcji spełniają określony warunek, na przykład czy wszystkie liczby są mniejsze od konkretnej wartości.

filter (iterable $iterable, callable $predicate): Generator

Tworzy nowy iterator zawierający tylko te elementy pierwotnego iteratora, które spełniają warunek zdefiniowany w $predicate. Callback $predicate ma sygnaturę function ($value, $key, iterable $iterable): bool i musi zwrócić true dla elementów, które mają zostać zachowane.

$iterator = new ArrayIterator([1, 2, 3]);
$iterator = Iterables::filter($iterator, fn($v) => $v < 3);
// 1, 2

Metoda używa generatora, co oznacza, że filtrowanie zachodzi stopniowo, w miarę przechodzenia po wyniku. Jest to oszczędne pamięciowo i pozwala przetwarzać bardzo duże kolekcje. Jeśli nie przejdziesz po wszystkich elementach wynikowego iteratora, oszczędzasz moc obliczeniową, bo nie wszystkie elementy pierwotnego iteratora zostaną przetworzone.

first (iterable $iterable, ?callable $predicate=null, ?callable $else=null)mixed

Zwraca pierwszy element iteratora. Jeśli podano $predicate, zwraca pierwszy element spełniający warunek. Callback $predicate ma sygnaturę function ($value, $key, iterable $iterable): bool. Jeśli żaden pasujący element się nie znajdzie, wywoływany jest callback $else (o ile go podano) i zwracany jest jego wynik. Jeśli $else nie podano, zwracane jest 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

Ta metoda przydaje się, gdy potrzebujesz szybko pobrać pierwszy element kolekcji albo pierwszy element spełniający konkretny warunek, bez ręcznego przechodzenia przez całą kolekcję.

firstKey (iterable $iterable, ?callable $predicate=null, ?callable $else=null)mixed

Zwraca klucz pierwszego elementu iteratora. Jeśli podano $predicate, zwraca klucz pierwszego elementu spełniającego warunek. Callback $predicate ma sygnaturę function ($value, $key, iterable $iterable): bool. Jeśli żaden pasujący element się nie znajdzie, wywoływany jest callback $else (o ile go podano) i zwracany jest jego wynik. Jeśli $else nie podano, zwracane jest 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

Tworzy nowy iterator, stosując callback $transformer do każdego elementu pierwotnego iteratora. Callback $transformer ma sygnaturę function ($value, $key, iterable $iterable): mixed, a zwracana przez niego wartość jest używana jako nowa wartość elementu.

$iterator = new ArrayIterator([1, 2, 3]);
$iterator = Iterables::map($iterator, fn($v) => $v * 2);
// 2, 4, 6

Metoda używa generatora, co oznacza, że przekształcanie zachodzi stopniowo, w miarę przechodzenia po wyniku. Jest to oszczędne pamięciowo i pozwala przetwarzać bardzo duże kolekcje. Jeśli nie przejdziesz po wszystkich elementach wynikowego iteratora, oszczędzasz moc obliczeniową, bo nie wszystkie elementy pierwotnego iteratora zostaną przetworzone.

mapWithKeys (iterable $iterable, callable $transformer): Generator

Tworzy nowy iterator, przekształcając wartości i klucze pierwotnego iteratora. Callback $transformer ma sygnaturę function ($value, $key, iterable $iterable): ?array{$newKey, $newValue}. Jeśli $transformer zwróci null, element zostanie pominięty. Dla zachowanych elementów pierwszy element zwróconej tablicy jest używany jako nowy klucz, a drugi jako nowa wartość.

$iterator = new ArrayIterator(['a' => 1, 'b' => 2]);
$iterator = Iterables::mapWithKeys($iterator, fn($v, $k) => $v > 1 ? [$v * 2, strtoupper($k)] : null);
// [4 => 'B']

Podobnie jak map(), ta metoda używa generatora do przetwarzania stopniowego i oszczędności pamięci. Pozwala to pracować z dużymi kolekcjami i oszczędzać moc obliczeniową przy tylko częściowym przejściu po wyniku.

memoize (iterable $iterable): IteratorAggregate

Tworzy wrapper wokół iteratora, który podczas iteracji cachuje jego klucze i wartości. Pozwala to wielokrotnie przechodzić po danych bez konieczności ponownego przechodzenia po pierwotnym źródle danych.

$iterator = /* ... */; // dane, po których nie da się iterować wielokrotnie
$memoized = Iterables::memoize($iterator);
// teraz możesz iterować po $memoized wiele razy bez utraty danych

Ta metoda przydaje się w sytuacjach, gdy potrzebujesz kilkakrotnie przejść po tym samym zestawie danych, ale pierwotny iterator nie pozwala na wielokrotną iterację albo ponowne przejście byłoby kosztowne (np. odczyt danych z bazy danych albo pliku).

repeatable (callable $factory): IteratorAggregate

Pozwala wielokrotnie iterować po obiektach, które normalnie tego nie obsługują, typowo po generatorach PHP. Metoda repeatable() rozwiązuje ten problem elegancko: zamiast przekazywać sam iterator, przekazujesz funkcję, która go tworzy. Ta fabryka jest potem automatycznie wywoływana przy każdej pętli iteracyjnej.

// zwykły generator, po którym nie da się iterować dwa razy
$generator = function () {
	yield 'A';
	yield 'B';
};

$iterator = Iterables::repeatable($generator);

foreach ($iterator as $v) echo $v; // wypisze: AB
foreach ($iterator as $v) echo $v; // wypisze: AB (generator uruchomił się ponownie)

Ta metoda jest alternatywą dla memoize() w sytuacjach, gdy pracujesz z dużymi ilościami danych, bo repeatable() nie cachuje danych, lecz generuje je ponownie przy każdej iteracji.

some (iterable $iterable, callable $predicate)bool

Sprawdza, czy co najmniej jeden element iteratora spełnia warunek zdefiniowany w $predicate. Callback $predicate ma sygnaturę function ($value, $key, iterable $iterable): bool i musi zwrócić true dla co najmniej jednego elementu, aby metoda some() zwróciła true.

$iterator = new ArrayIterator([1, 30, 39, 29, 10, 13]);
$isEven = fn($value) => $value % 2 === 0;
$res = Iterables::some($iterator, $isEven); // true

Ta metoda przydaje się do szybkiego sprawdzenia, czy co najmniej jeden element kolekcji spełnia określony warunek, na przykład czy kolekcja zawiera co najmniej jedną liczbę parzystą.

Zobacz every().

toIterator (iterable $iterable): Iterator

Konwertuje dowolny obiekt iterowalny (tablicę, Traversable) na Iterator. Jeśli wejściem jest już Iterator, zwracany jest bez zmian.

$array = [1, 2, 3];
$iterator = Iterables::toIterator($array);
// teraz masz Iterator zamiast tablicy

Ta metoda przydaje się, gdy potrzebujesz mieć pewność, że dysponujesz Iteratorem, niezależnie od typu danych wejściowych. Bywa to pomocne przy tworzeniu funkcji pracujących z różnymi typami danych iterowalnych.

wersja: 4.x