From f73dca400af6cbf2fefd39f2a5f0dda6e93f9696 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 2 May 2024 23:05:45 +0200 Subject: [PATCH] filters |sort & |group returns KeyValueIterator allows serial & parallel iterations --- src/Latte/Essential/Filters.php | 32 ++++------- src/Latte/Essential/KeyValueIterator.php | 30 ++++++++++ tests/common/KeyValueIterator.phpt | 70 ++++++++++++++++++++++++ tests/filters/group.phpt | 46 +++++++++++++++- tests/filters/sort.phpt | 15 +++++ 5 files changed, 172 insertions(+), 21 deletions(-) create mode 100644 src/Latte/Essential/KeyValueIterator.php create mode 100644 tests/common/KeyValueIterator.phpt diff --git a/src/Latte/Essential/Filters.php b/src/Latte/Essential/Filters.php index 0705bcdf5..10b9684ba 100644 --- a/src/Latte/Essential/Filters.php +++ b/src/Latte/Essential/Filters.php @@ -458,28 +458,23 @@ public static function sort(iterable $iterable, ?\Closure $comparison = null): i return $iterable; } - $keys = $values = []; + $pairs = []; foreach ($iterable as $key => $value) { - $keys[] = $key; - $values[] = $value; + $pairs[] = [$key, $value]; } - $comparison ? uasort($values, $comparison) : asort($values); + uasort($pairs, fn($a, $b) => $comparison ? $comparison($a[1], $b[1]) : $a[1] <=> $b[1]); - return (static function () use ($keys, $values): \Generator { - foreach ($values as $i => $value) { - yield $keys[$i] => $value; - } - })(); + return new KeyValueIterator($pairs); } /** * Groups elements by the element indices and preserves the key association and order. */ - public static function group(iterable $iterable, string|int|\Closure $by): \Generator + public static function group(iterable $iterable, string|int|\Closure $by): \Traversable { $fn = $by instanceof \Closure ? $by : fn($a) => is_array($a) ? $a[$by] : $a->$by; - $keys = $groups = $prevKey = []; + $keys = $groups = []; foreach ($iterable as $k => $v) { $groupKey = $fn($v, $k); @@ -491,17 +486,14 @@ public static function group(iterable $iterable, string|int|\Closure $by): \Gene } $prevKey = $groupKey; } - $groups[$index][0][] = $k; - $groups[$index][1][] = $v; + $groups[$index][] = [$k, $v]; } - foreach ($groups as $index => $pair) { - yield $keys[$index] => (static function () use ($pair): \Generator { - foreach ($pair[1] as $i => $value) { - yield $pair[0][$i] => $value; - } - })(); - } + return new KeyValueIterator(array_map( + fn($key, $group) => [$key, new KeyValueIterator($group)], + $keys, + $groups, + )); } diff --git a/src/Latte/Essential/KeyValueIterator.php b/src/Latte/Essential/KeyValueIterator.php new file mode 100644 index 000000000..78c47dcec --- /dev/null +++ b/src/Latte/Essential/KeyValueIterator.php @@ -0,0 +1,30 @@ +pairs as [$key, $value]) { + yield $key => $value; + } + } +} diff --git a/tests/common/KeyValueIterator.phpt b/tests/common/KeyValueIterator.phpt new file mode 100644 index 000000000..f85cf03c3 --- /dev/null +++ b/tests/common/KeyValueIterator.phpt @@ -0,0 +1,70 @@ + $value) { + $res[] = [$key, $value]; + } + return $res; +} + + +test('empty array', function () { + Assert::same( + [], + exportIterator(new KeyValueIterator([])), + ); +}); + + +test('single usage', function () { + $pairs = [[new stdClass, new stdClass], [null, null]]; + $iterator = new KeyValueIterator($pairs); + Assert::same( + $pairs, + exportIterator($iterator), + ); +}); + + +test('serial usage', function () { + $pairs = [[0, 0], [1, 1], [2, 2]]; + $iterator = new KeyValueIterator($pairs); + Assert::same( + $pairs, + exportIterator($iterator), + ); + Assert::same( + $pairs, + exportIterator($iterator), + ); +}); + + +test('parallel usage', function () { + $pairs = [[0, 0], [1, 1], [2, 2]]; + $keys = []; + $iterator = new KeyValueIterator($pairs); + foreach ($iterator as $key => $value) { + $keys[] = $key; + foreach ($iterator as $value); + } + + Assert::same( + [0, 1, 2], + $keys, + ); +}); diff --git a/tests/filters/group.phpt b/tests/filters/group.phpt index ac86d3685..5e984e588 100644 --- a/tests/filters/group.phpt +++ b/tests/filters/group.phpt @@ -15,8 +15,8 @@ require __DIR__ . '/../bootstrap.php'; function iterator(): Generator { yield ['a' => 55] => ['k' => 22, 'k2']; - yield ['a' => 66] => (object) ['k' => 22, 'k2']; yield ['a' => 77] => ['k' => 11]; + yield ['a' => 66] => (object) ['k' => 22, 'k2']; yield ['a' => 88] => ['k' => 33]; } @@ -65,6 +65,50 @@ test('iterator', function () { }); +test('serial usage', function () { + $groups = Filters::group(iterator(), 'k'); + + Assert::equal( + [ + [22, [ + [['a' => 55], ['k' => 22, 'k2']], + [['a' => 66], (object) ['k' => 22, 'k2']], + ]], + [11, [[['a' => 77], ['k' => 11]]]], + [33, [[['a' => 88], ['k' => 33]]]], + ], + exportIterator($groups), + ); + + Assert::equal( + [ + [22, [ + [['a' => 55], ['k' => 22, 'k2']], + [['a' => 66], (object) ['k' => 22, 'k2']], + ]], + [11, [[['a' => 77], ['k' => 11]]]], + [33, [[['a' => 88], ['k' => 33]]]], + ], + exportIterator($groups), + ); +}); + + +test('parallel usage', function () { + $groups = Filters::group(iterator(), 'k'); + $keys = []; + foreach ($groups as $key => $group) { + $keys[] = $key; + foreach ($groups as $key => $group); + } + + Assert::equal( + [22, 11, 33], + $keys, + ); +}); + + test('array + callback', function () { Assert::same( [[220, [[0, 22]]], [110, [[1, 11]]], [330, [[2, 33]]]], diff --git a/tests/filters/sort.phpt b/tests/filters/sort.phpt index 99d62cd6f..0301cd094 100644 --- a/tests/filters/sort.phpt +++ b/tests/filters/sort.phpt @@ -44,6 +44,21 @@ test('iterator', function () { }); +test('multiple calls', function () { + $sorted = Filters::sort(iterator()); + + Assert::same( + [['b', 10], ['a', 20], [[true], 30]], + exportIterator($sorted), + ); + + Assert::same( + [['b', 10], ['a', 20], [[true], 30]], + exportIterator($sorted), + ); +}); + + test('user comparison + array', function () { Assert::same( [2 => 30, 0 => 20, 1 => 10],