Skip to content

Commit

Permalink
Add chunk() (#110)
Browse files Browse the repository at this point in the history
Fixes #109
  • Loading branch information
sanmai committed Nov 30, 2022
1 parent 1c14946 commit 929b115
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 16 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -114,6 +114,7 @@ All entry points always return an instance of the pipeline.
| `unshift()` | Prepends the pipeline with a list of values. | `array_unshift` |
| `zip()` | Takes a number of iterables, merging them together with the current sequence, if any. | `array_map(null, ...$array)`, Python's `zip()`, transposition |
| `unpack()` | Unpacks arrays into arguments for a callback. Flattens inputs if no callback provided. | `flat_map`, `flatten` |
| `chunk()` | Chunks the pipeline into arrays of specified length. | `array_chunk` |
| `filter()` | Removes elements unless a callback returns true. Removes falsey values if no callback provided. | `array_filter`, `Where` |
| `slice()` | Extracts a slice from the inputs. Keys are not discarded intentionally. Suppors negative values for both arguments. | `array_slice` |
| `reduce()` | Reduces input values to a single value. Defaults to summation. Requires an initial value. | `array_reduce`, `Aggregate`, `Sum` |
Expand Down
53 changes: 50 additions & 3 deletions src/Standard.php
Expand Up @@ -19,6 +19,7 @@

namespace Pipeline;

use function array_chunk;
use function array_filter;
use function array_flip;
use function array_map;
Expand Down Expand Up @@ -211,6 +212,52 @@ public function unpack(?callable $func = null): self
});
}

/**
* Chunks the pipeline into arrays with length elements. The last chunk may contain less than length elements.
*
* @param int<1, max> $length the size of each chunk
* @param bool $preserve_keys When set to true keys will be preserved. Default is false which will reindex the chunk numerically.
*
* @return $this
*/
public function chunk(int $length, bool $preserve_keys = false): self
{
if (null === $this->pipeline) {
// No-op: null.
return $this;
}

if ([] === $this->pipeline) {
// No-op: an empty array.
return $this;
}

// Array shortcut
if (is_array($this->pipeline)) {
$this->pipeline = array_chunk($this->pipeline, $length, $preserve_keys);

return $this;
}

$this->pipeline = self::toChunks(
self::makeNonRewindable($this->pipeline),
$length,
$preserve_keys
);

return $this;
}

/**
* @psalm-param positive-int $length
*/
private static function toChunks(Generator $input, int $length, bool $preserve_keys): iterable
{
while ($input->valid()) {
yield iterator_to_array(self::take($input, $length), $preserve_keys);
}
}

/**
* Takes a callback that for each input value may return one or yield many. Also takes an initial generator, where it must not require any arguments.
*
Expand Down Expand Up @@ -446,7 +493,7 @@ public function getIterator(): Traversable
/**
* By default returns all values regardless of keys used, discarding all keys in the process. Has an option to keep the keys. This is a terminal operation.
*/
public function toArray(bool $useKeys = false): array
public function toArray(bool $preserve_keys = false): array
{
if (null === $this->pipeline) {
// With non-primed pipeline just return an empty array.
Expand All @@ -460,7 +507,7 @@ public function toArray(bool $useKeys = false): array

// We got what we need, moving along.
if (is_array($this->pipeline)) {
if ($useKeys) {
if ($preserve_keys) {
return $this->pipeline;
}

Expand All @@ -469,7 +516,7 @@ public function toArray(bool $useKeys = false): array

// Because `yield from` does not reset keys we have to ignore them on export by default to return every item.
// http://php.net/manual/en/language.generators.syntax.php#control-structures.yield.from
return iterator_to_array($this, $useKeys);
return iterator_to_array($this, $preserve_keys);
}

/**
Expand Down
16 changes: 8 additions & 8 deletions tests/AppendPrependTest.php
Expand Up @@ -85,8 +85,8 @@ public function testPush(array $expected, ?array $initialValue, ...$iterables):
$pipeline->push(...$iterable ?? []);
}

$useKeys = !is_numeric(key($expected));
$this->assertSame($expected, $pipeline->toArray($useKeys));
$preserve_keys = !is_numeric(key($expected));
$this->assertSame($expected, $pipeline->toArray($preserve_keys));
}

public function provideAppend(): iterable
Expand All @@ -109,8 +109,8 @@ public function testAppend(array $expected, ?iterable $initialValue, ...$iterabl
$pipeline->append($iterable);
}

$useKeys = !is_numeric(key($expected));
$this->assertSame($expected, $pipeline->toArray($useKeys));
$preserve_keys = !is_numeric(key($expected));
$this->assertSame($expected, $pipeline->toArray($preserve_keys));
}

public function providePrependArrays(): iterable
Expand Down Expand Up @@ -145,8 +145,8 @@ public function testUnshift(array $expected, ?array $initialValue, ...$iterables
$pipeline->unshift(...$iterable ?? []);
}

$useKeys = !is_numeric(key($expected));
$this->assertSame($expected, $pipeline->toArray($useKeys));
$preserve_keys = !is_numeric(key($expected));
$this->assertSame($expected, $pipeline->toArray($preserve_keys));
}

public function providePrepend(): iterable
Expand All @@ -169,8 +169,8 @@ public function testPrepend(array $expected, ?iterable $initialValue, ...$iterab
$pipeline->prepend($iterable);
}

$useKeys = !is_numeric(key($expected));
$this->assertSame($expected, $pipeline->toArray($useKeys));
$preserve_keys = !is_numeric(key($expected));
$this->assertSame($expected, $pipeline->toArray($preserve_keys));
}

private function skipOnPHP7(array $expected): void
Expand Down
93 changes: 93 additions & 0 deletions tests/ChunkTest.php
@@ -0,0 +1,93 @@
<?php
/**
* Copyright 2017, 2018 Alexey Kopytko <alexey@kopytko.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

declare(strict_types=1);

namespace Tests\Pipeline;

use ArrayIterator;
use IteratorIterator;
use PHPUnit\Framework\TestCase;
use function Pipeline\fromArray;
use Pipeline\Standard;
use function Pipeline\take;

/**
* @covers \Pipeline\Standard
*
* @internal
*/
final class ChunkTest extends TestCase
{
public function provideArrays(): iterable
{
yield [false, 3, [], []];

yield [false, 3, [1, 2, 3, 4, 5], [[1, 2, 3], [4, 5]]];

yield [false, 1, [1, 2, 3, 4, 5], [[1], [2], [3], [4], [5]]];

yield [true, 3, [1, 2, 3, 4, 5], [[1, 2, 3], [3 => 4, 4 => 5]]];

yield [true, 2, ['a' => 1, 'b' => 2, 'c' => 3], [['a' => 1, 'b' => 2], ['c' => 3]]];

yield [false, 2, ['a' => 1, 'b' => 2, 'c' => 3], [[1, 2], [3]]];
}

public function provideIterables(): iterable
{
foreach ($this->provideArrays() as $item) {
yield $item;

$iteratorItem = $item;
$iteratorItem[2] = new ArrayIterator($iteratorItem[2]);

yield $iteratorItem;

$iteratorItem = $item;
$iteratorItem[2] = new IteratorIterator(new ArrayIterator($iteratorItem[2]));

yield $iteratorItem;

$iteratorItem = $item;
$iteratorItem[2] = fromArray($iteratorItem[2]);

yield $iteratorItem;
}
}

/**
* @dataProvider provideIterables
*/
public function testChunk(bool $preserve_keys, int $length, iterable $input, array $expected): void
{
$pipeline = take($input);

$pipeline->chunk($length, $preserve_keys);

$this->assertSame($expected, $pipeline->toArray($preserve_keys));
}

public function testChunkNoop(): void
{
$pipeline = new Standard();

$pipeline->chunk(100);

$this->assertSame([], $pipeline->toArray());
}
}
10 changes: 5 additions & 5 deletions tests/SliceTest.php
Expand Up @@ -181,7 +181,7 @@ public static function specimens(): iterable
'input' => ['one' => 1, 'two' => 2, 3, 23 => 4],
'offset' => 2,
'length' => 2,
'useKeys' => true,
'preserve_keys' => true,
];

yield [
Expand Down Expand Up @@ -270,13 +270,13 @@ public static function specimens(): iterable
*
* @covers \Pipeline\Standard::slice()
*/
public function testSliceWithArrays(array $expected, array $input, int $offset, ?int $length = null, bool $useKeys = false): void
public function testSliceWithArrays(array $expected, array $input, int $offset, ?int $length = null, bool $preserve_keys = false): void
{
$pipeline = fromArray($input);

$this->assertSame(
$expected,
$pipeline->slice($offset, $length)->toArray($useKeys)
$pipeline->slice($offset, $length)->toArray($preserve_keys)
);
}

Expand All @@ -285,7 +285,7 @@ public function testSliceWithArrays(array $expected, array $input, int $offset,
*
* @covers \Pipeline\Standard::slice()
*/
public function testSliceWithIterables(array $expected, array $input, int $offset, ?int $length = null, bool $useKeys = false): void
public function testSliceWithIterables(array $expected, array $input, int $offset, ?int $length = null, bool $preserve_keys = false): void
{
$pipeline = map(static function () use ($input) {
yield from $input;
Expand All @@ -294,7 +294,7 @@ public function testSliceWithIterables(array $expected, array $input, int $offse
try {
$this->assertSame(
$expected,
$pipeline->slice($offset, $length)->toArray($useKeys)
$pipeline->slice($offset, $length)->toArray($preserve_keys)
);
} catch (InvalidArgumentException $e) {
if ('Not implemented yet' === $e->getMessage()) {
Expand Down

0 comments on commit 929b115

Please sign in to comment.