Skip to content

Commit

Permalink
filters |sort & |group returns KeyValueIterator
Browse files Browse the repository at this point in the history
allows serial & parallel iterations
  • Loading branch information
dg committed May 2, 2024
1 parent 9753ccf commit f73dca4
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 21 deletions.
32 changes: 12 additions & 20 deletions src/Latte/Essential/Filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
));
}


Expand Down
30 changes: 30 additions & 0 deletions src/Latte/Essential/KeyValueIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

/**
* This file is part of the Latte (https://latte.nette.org)
* Copyright (c) 2008 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Latte\Essential;


/**
* @internal
*/
class KeyValueIterator implements \IteratorAggregate
{
public function __construct(
private readonly array $pairs,
) {
}


public function getIterator(): \Generator
{
foreach ($this->pairs as [$key, $value]) {
yield $key => $value;
}
}
}
70 changes: 70 additions & 0 deletions tests/common/KeyValueIterator.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

/**
* Test: KeyValueIterator usage.
*/

declare(strict_types=1);

use Latte\Essential\KeyValueIterator;
use Tester\Assert;

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


function exportIterator(iterable $iterator): array
{
$res = [];
foreach ($iterator as $key => $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,
);
});
46 changes: 45 additions & 1 deletion tests/filters/group.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

Expand Down Expand Up @@ -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]]]],
Expand Down
15 changes: 15 additions & 0 deletions tests/filters/sort.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down

0 comments on commit f73dca4

Please sign in to comment.