add chunk to split values into arrays of a size
This commit is contained in:
parent
ab3034e2be
commit
a61db75c7e
@ -253,7 +253,7 @@ array. The details:
|
|||||||
|
|
||||||
¹ `map`, `filter`, `reject`, `unique`, `uniqueBy`, `sort`, `sortBy`, `slice`, `take`, `skip`, `merge`, `each`,
|
¹ `map`, `filter`, `reject`, `unique`, `uniqueBy`, `sort`, `sortBy`, `slice`, `take`, `skip`, `merge`, `each`,
|
||||||
`tap`. ² `flatMap`, `values`, `keys`, `prepend`, `append`, `concat`: these produce a list, and a `Ds\Map` can't hold
|
`tap`. ² `flatMap`, `values`, `keys`, `prepend`, `append`, `concat`: these produce a list, and a `Ds\Map` can't hold
|
||||||
a list, so it becomes a `Ds\Seq`.
|
a list, so it becomes a `Ds\Seq`. `chunk` also produces a list, of arrays, and gives a `Ds\Seq` for a `Ds\Set` too.
|
||||||
|
|
||||||
A few consequences worth knowing:
|
A few consequences worth knowing:
|
||||||
|
|
||||||
@ -311,7 +311,8 @@ exactly like its target:
|
|||||||
|
|
||||||
`map`, `filter` (keeps truthy values without a callback), `reject`, `flatMap`, `values`, `keys`, `unique`,
|
`map`, `filter` (keeps truthy values without a callback), `reject`, `flatMap`, `values`, `keys`, `unique`,
|
||||||
`uniqueBy`, `sort` (comparator or `<=>`), `sortBy` (selector, optionally `descending: true`), `slice`, `take`,
|
`uniqueBy`, `sort` (comparator or `<=>`), `sortBy` (selector, optionally `descending: true`), `slice`, `take`,
|
||||||
`skip`, `prepend`, `append`, `concat`, `merge`, `each`, `tap`.
|
`skip`, `chunk` (size, optionally `preserveKeys: true`, like `array_chunk()`), `prepend`, `append`, `concat`, `merge`,
|
||||||
|
`each`, `tap`.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$delays = $departures
|
$delays = $departures
|
||||||
|
|||||||
@ -12,7 +12,8 @@
|
|||||||
*
|
*
|
||||||
* Every function returns a closure that takes any iterable and returns a generator, so nothing is done until the
|
* Every function returns a closure that takes any iterable and returns a generator, so nothing is done until the
|
||||||
* result is consumed and a chain walks its source once. Keys are preserved, except for {@see values()},
|
* result is consumed and a chain walks its source once. Keys are preserved, except for {@see values()},
|
||||||
* {@see keys()}, {@see flatMap()}, {@see prepend()}, {@see append()} and {@see concat()}, which yield sequential keys.
|
* {@see keys()}, {@see flatMap()}, {@see prepend()}, {@see append()}, {@see concat()} and {@see chunk()}, which yield
|
||||||
|
* sequential keys.
|
||||||
* End a chain with a collector or query from `Kadet\Functional\Collections`, e.g. `toArray()`.
|
* End a chain with a collector or query from `Kadet\Functional\Collections`, e.g. `toArray()`.
|
||||||
*
|
*
|
||||||
* The eager transforms in `Kadet\Functional\Collections` are built on these, so both flavours behave the same.
|
* The eager transforms in `Kadet\Functional\Collections` are built on these, so both flavours behave the same.
|
||||||
@ -257,6 +258,42 @@ function skip(int $n): \Closure
|
|||||||
return slice($n);
|
return slice($n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits the values into arrays of `$size` (the last one may be shorter), like `array_chunk()`, yielding each as soon
|
||||||
|
* as it is full, so the source is never held whole.
|
||||||
|
*
|
||||||
|
* Chunks are lists unless `$preserveKeys` is true, which keeps the keys in them. Keys must then be valid array keys,
|
||||||
|
* and values under a key repeated within one chunk replace each other, as in any array.
|
||||||
|
*
|
||||||
|
* @return \Closure<TKey, TValue>(iterable<TKey, TValue>): \Generator<int, array<TKey, TValue>>
|
||||||
|
*/
|
||||||
|
function chunk(int $size, bool $preserveKeys = false): \Closure
|
||||||
|
{
|
||||||
|
if ($size < 1) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('chunk(): size must be >= 1, got %d', $size));
|
||||||
|
}
|
||||||
|
|
||||||
|
return static function (iterable $input) use ($size, $preserveKeys): \Generator {
|
||||||
|
$chunk = [];
|
||||||
|
foreach ($input as $key => $value) {
|
||||||
|
if ($preserveKeys) {
|
||||||
|
$chunk[$key] = $value;
|
||||||
|
} else {
|
||||||
|
$chunk[] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($chunk) === $size) {
|
||||||
|
yield $chunk;
|
||||||
|
$chunk = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($chunk !== []) {
|
||||||
|
yield $chunk;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts `$values` before the values of the input, with sequential keys.
|
* Puts `$values` before the values of the input, with sequential keys.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -490,6 +490,25 @@ function skip(int $n): \Closure
|
|||||||
return slice($n);
|
return slice($n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits the values into arrays of `$size` (the last one may be shorter), like `array_chunk()`. See
|
||||||
|
* {@see Lazy\chunk()}.
|
||||||
|
*
|
||||||
|
* The chunks come in a list array for an array, and in a `Ds\Seq` (a list array without php-ds) for anything else.
|
||||||
|
*
|
||||||
|
* @return \Closure<TKey, TValue>(iterable<TKey, TValue>): iterable<int, array<TKey, TValue>>
|
||||||
|
*/
|
||||||
|
function chunk(int $size, bool $preserveKeys = false): \Closure
|
||||||
|
{
|
||||||
|
$lazy = lazy\chunk($size, $preserveKeys);
|
||||||
|
|
||||||
|
return static function (iterable $input) use ($lazy): iterable {
|
||||||
|
$chunks = iterator_to_array($lazy(iterated($input)), false);
|
||||||
|
|
||||||
|
return is_array($input) ? $chunks : seqOf($chunks);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts `$values` before the values of the input, as a list (keys are dropped).
|
* Puts `$values` before the values of the input, as a list (keys are dropped).
|
||||||
*
|
*
|
||||||
|
|||||||
@ -167,4 +167,48 @@ class LazyTest extends \PHPUnit\Framework\TestCase
|
|||||||
$this->assertSame([2, 3], iterator_to_array($step([1, 2])));
|
$this->assertSame([2, 3], iterator_to_array($step([1, 2])));
|
||||||
$this->assertSame([4], iterator_to_array($step([3])));
|
$this->assertSame([4], iterator_to_array($step([3])));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testChunkSplitsIntoListsOfTheSize()
|
||||||
|
{
|
||||||
|
$this->assertSame(
|
||||||
|
[[1, 2], [3, 4], [5]],
|
||||||
|
iterator_to_array(lazy\chunk(2)(self::gen(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5]))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testChunkPreservesKeysWhenAsked()
|
||||||
|
{
|
||||||
|
$this->assertSame(
|
||||||
|
[['a' => 1, 'b' => 2], ['c' => 3]],
|
||||||
|
iterator_to_array(lazy\chunk(2, preserveKeys: true)(self::gen(['a' => 1, 'b' => 2, 'c' => 3]))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testChunkKeepsValuesUnderRepeatedKeys()
|
||||||
|
{
|
||||||
|
$repeated = (function () {
|
||||||
|
yield 'a' => 1;
|
||||||
|
yield 'a' => 2;
|
||||||
|
})();
|
||||||
|
|
||||||
|
$this->assertSame([[1, 2]], iterator_to_array(lazy\chunk(2)($repeated)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testChunkYieldsEachChunkOnceFull()
|
||||||
|
{
|
||||||
|
$this->assertSame([1, 2, 3], lazy\chunk(3)(self::naturals())->current());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testChunkOfNothingYieldsNothing()
|
||||||
|
{
|
||||||
|
$this->assertSame([], iterator_to_array(lazy\chunk(2)([])));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testChunkRejectsSizeBelowOneWhenBuilt()
|
||||||
|
{
|
||||||
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('chunk(): size must be >= 1, got 0');
|
||||||
|
|
||||||
|
lazy\chunk(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -426,6 +426,23 @@ class TransformsTest extends \PHPUnit\Framework\TestCase
|
|||||||
$this->assertSeq([1, 2, 3], c\take(3)($infinite));
|
$this->assertSeq([1, 2, 3], c\take(3)($infinite));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testChunk()
|
||||||
|
{
|
||||||
|
$this->assertSame([[1, 2], [3]], c\chunk(2)(['a' => 1, 'b' => 2, 'c' => 3]));
|
||||||
|
$this->assertSame([['a' => 1, 'b' => 2], ['c' => 3]], c\chunk(2, preserveKeys: true)(['a' => 1, 'b' => 2, 'c' => 3]));
|
||||||
|
$this->assertSeq([[1, 2], [3]], c\chunk(2)(new Seq([1, 2, 3])));
|
||||||
|
$this->assertSeq([['a' => 1], ['b' => 2]], c\chunk(1, preserveKeys: true)(new Map(['a' => 1, 'b' => 2])));
|
||||||
|
$this->assertSeq([[1, 2], [3]], c\chunk(2)(self::gen([1, 2, 3])));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testChunkRejectsSizeBelowOne()
|
||||||
|
{
|
||||||
|
$this->expectException(\InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('chunk(): size must be >= 1, got 0');
|
||||||
|
|
||||||
|
c\chunk(0);
|
||||||
|
}
|
||||||
|
|
||||||
public function testSkip()
|
public function testSkip()
|
||||||
{
|
{
|
||||||
$this->assertSame(['b' => 2], c\skip(1)(['a' => 1, 'b' => 2]));
|
$this->assertSame(['b' => 2], c\skip(1)(['a' => 1, 'b' => 2]));
|
||||||
|
|||||||
@ -67,10 +67,12 @@ function transforms(
|
|||||||
// Transforms without a callback keep the element types, but not the container.
|
// Transforms without a callback keep the element types, but not the container.
|
||||||
assertType('iterable<int, Foo>', c\values()($foos));
|
assertType('iterable<int, Foo>', c\values()($foos));
|
||||||
assertType('iterable<int<0, max>, Foo>', c\take(2)($list));
|
assertType('iterable<int<0, max>, Foo>', c\take(2)($list));
|
||||||
|
assertType('iterable<int, array<int<0, max>, Foo>>', c\chunk(2)($list));
|
||||||
|
|
||||||
// Lazy transforms give generators.
|
// Lazy transforms give generators.
|
||||||
assertType('Generator<mixed, string, mixed, mixed>', lazy\map(fn (Foo $foo): string => $foo->name)($list));
|
assertType('Generator<mixed, string, mixed, mixed>', lazy\map(fn (Foo $foo): string => $foo->name)($list));
|
||||||
assertType('Generator<mixed, Foo, mixed, mixed>', lazy\filter(fn (Foo $foo): bool => $foo->id > 0)($list));
|
assertType('Generator<mixed, Foo, mixed, mixed>', lazy\filter(fn (Foo $foo): bool => $foo->id > 0)($list));
|
||||||
|
assertType('Generator<int, array<int<0, max>, Foo>, mixed, mixed>', lazy\chunk(2)($list));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user