Skip to content

Commit

Permalink
added filter |group & function group()
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed May 14, 2024
1 parent ff82385 commit 8998520
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/Latte/Essential/CoreExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ public function getFilters(): array
? [$this->filters, 'firstUpper']
: fn() => throw new RuntimeException('Filter |firstUpper requires mbstring extension.'),
'floor' => [$this->filters, 'floor'],
'group' => [$this->filters, 'group'],
'implode' => [$this->filters, 'implode'],
'indent' => [$this->filters, 'indent'],
'join' => [$this->filters, 'implode'],
Expand Down Expand Up @@ -181,6 +182,7 @@ public function getFunctions(): array
'divisibleBy' => [$this->filters, 'divisibleBy'],
'even' => [$this->filters, 'even'],
'first' => [$this->filters, 'first'],
'group' => [$this->filters, 'group'],
'last' => [$this->filters, 'last'],
'odd' => [$this->filters, 'odd'],
'slice' => [$this->filters, 'slice'],
Expand Down
33 changes: 33 additions & 0 deletions src/Latte/Essential/Filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,39 @@ public static function sort(
}


/**
* Groups elements by the element indices and preserves the key association and order.
* @template K
* @template V
* @param iterable<K, V> $data
* @return iterable<iterable<K, V>>
*/
public static function group(iterable $data, string|int|\Closure $by): iterable
{
$fn = $by instanceof \Closure ? $by : fn($a) => is_array($a) ? $a[$by] : $a->$by;
$keys = $groups = [];

foreach ($data as $k => $v) {
$groupKey = $fn($v, $k);
if (!$groups || $prevKey !== $groupKey) {
$index = array_search($groupKey, $keys, true);
if ($index === false) {
$index = count($keys);
$keys[$index] = $groupKey;
}
$prevKey = $groupKey;
}
$groups[$index][] = [$k, $v];
}

return new AuxiliaryIterator(array_map(
fn($key, $group) => [$key, new AuxiliaryIterator($group)],
$keys,
$groups,
));
}


/**
* Returns value clamped to the inclusive range of min and max.
*/
Expand Down
127 changes: 127 additions & 0 deletions tests/filters/group.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

/**
* Test: Latte\Essential\Filters::group()
*/

declare(strict_types=1);

use Latte\Essential\Filters;
use Tester\Assert;

require __DIR__ . '/../bootstrap.php';


function iterator(): Generator
{
yield ['a' => 55] => ['k' => 22, 'k2'];
yield ['a' => 77] => ['k' => 11];
yield ['a' => 66] => (object) ['k' => 22, 'k2'];
yield ['a' => 88] => ['k' => 33];
}


function exportIterator(Traversable $iterator): array
{
$res = [];
foreach ($iterator as $key => $value) {
$res[] = [$key, $value instanceof Traversable ? exportIterator($value) : $value];
}
return $res;
}


test('array', function () {
Assert::equal(
[
[22, [
[0, ['k' => 22, 'k2']],
[1, (object) ['k' => 22, 'k2']],
]],
[11, [[2, ['k' => 11]]]],
[33, [[3, ['k' => 33]]]],
],
exportIterator(Filters::group(
[['k' => 22, 'k2'], (object) ['k' => 22, 'k2'], ['k' => 11], ['k' => 33]],
'k',
)),
);
Assert::same([], exportIterator(Filters::group([], 'k')));
});


test('iterator', function () {
$groups = Filters::group(iterator(), 'k');

Assert::same(3, count($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('re-iteration', function () {
$groups = Filters::group(iterator(), 'k');
$res = [
[22, [
[['a' => 55], ['k' => 22, 'k2']],
[['a' => 66], (object) ['k' => 22, 'k2']],
]],
[11, [[['a' => 77], ['k' => 11]]]],
[33, [[['a' => 88], ['k' => 33]]]],
];
Assert::equal(
$res,
exportIterator($groups),
);
Assert::equal(
$res,
exportIterator($groups),
);
});


test('nested re-iteration', function () {
$groups = Filters::group(iterator(), 'k');
$keys = [];
foreach ($groups as $key => $group) {
$keys[] = $key;
foreach ($groups as $group);
}

Assert::equal(
[22, 11, 33],
$keys,
);
});


test('array + callback', function () {
Assert::same(
[[220, [[0, 22]]], [110, [[1, 11]]], [330, [[2, 33]]]],
exportIterator(Filters::group([22, 11, 33], fn($a) => $a * 10)),
);
});


test('iterator + callback', function () {
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(Filters::group(iterator(), fn($a) => -((array) $a)['k'])),
);
});

0 comments on commit 8998520

Please sign in to comment.