Skip to content

Commit

Permalink
Merge e90d579 into d64f62c
Browse files Browse the repository at this point in the history
  • Loading branch information
sanmai committed Nov 8, 2022
2 parents d64f62c + e90d579 commit a7045e3
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 9 deletions.
117 changes: 114 additions & 3 deletions src/Standard.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
use function array_filter;
use function array_flip;
use function array_map;
use function array_merge;
use function array_reduce;
use function array_shift;
use function array_slice;
use function array_values;
use ArrayIterator;
use function assert;
use CallbackFilterIterator;
use function count;
use Countable;
Expand Down Expand Up @@ -72,6 +74,118 @@ public function __construct(iterable $input = null)
$this->pipeline = $input;
}

/**
* Appends the contents of an interable to the end of the pipeline.
*
* @param ?iterable $values
*/
public function append(iterable $values = null): self
{
// Do we need to do anything here?
if ($this->willReplace($values)) {
return $this;
}

// Static analyzer hints
assert(null !== $this->pipeline);
assert(null !== $values);

return $this->join($this->pipeline, $values);
}

/**
* Appends a list of values to the end of the pipeline.
*
* @param mixed ...$vector
*/
public function push(...$vector): self
{
return $this->append($vector);
}

/**
* Prepends the pipeline with the contents of an iterable.
*
* @param ?iterable $values
*/
public function prepend(iterable $values = null): self
{
// Do we need to do anything here?
if ($this->willReplace($values)) {
return $this;
}

// Static analyzer hints
assert(null !== $this->pipeline);
assert(null !== $values);

return $this->join($values, $this->pipeline);
}

/**
* Prepends the pipeline with a list of values.
*
* @param mixed ...$vector
*/
public function unshift(...$vector): self
{
return $this->prepend($vector);
}

/**
* Determine if the internal pipeline will be replaced when appending/prepending.
*
* Utility method for appending/prepending methods.
*/
private function willReplace(iterable $values = null): bool
{
// Nothing needs to be done here.
/** @phan-suppress-next-line PhanTypeComparisonFromArray */
if (null === $values || [] === $values) {
return true;
}

// No shortcuts are applicable if the pipeline was initialized.
if ([] !== $this->pipeline && null !== $this->pipeline) {
return false;
}

// Install an array as it is.
if (is_array($values)) {
$this->pipeline = $values;

return true;
}

// Else we use ownself to handle edge cases.
$this->pipeline = new self($values);

return true;
}

/**
* Replace the internal pipeline with a combination of two non-empty iterables.
*
* Utility method for appending/prepending methods.
*/
private function join(iterable $left, iterable $right): self
{
// We got two arrays, that's what we will use.
if (is_array($left) && is_array($right)) {
$this->pipeline = array_merge($left, $right);

return $this;
}

// Last, join the hard way.
$this->pipeline = (static function () use ($left, $right) {
yield from $left;
yield from $right;
})();

return $this;
}

/**
* An extra variant of `map` which unpacks arrays into arguments. Flattens inputs if no callback provided.
*
Expand Down Expand Up @@ -109,7 +223,6 @@ public function map(?callable $func = null): self

// That's the standard case for any next stages.
if (is_iterable($this->pipeline)) {
/** @phan-suppress-next-line PhanTypeMismatchArgument */
$this->pipeline = self::apply($this->pipeline, $func);

return $this;
Expand Down Expand Up @@ -178,7 +291,6 @@ public function cast(callable $func = null): self
}

if (is_iterable($this->pipeline)) {
/** @phan-suppress-next-line PhanTypeMismatchArgument */
$this->pipeline = self::applyOnce($this->pipeline, $func);

return $this;
Expand Down Expand Up @@ -234,7 +346,6 @@ public function filter(?callable $func = null): self
/** @var Iterator $iterator */
$iterator = $this->pipeline;

/** @phan-suppress-next-line PhanTypeMismatchArgumentInternal */
$this->pipeline = new CallbackFilterIterator($iterator, $func);

return $this;
Expand Down
18 changes: 16 additions & 2 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,30 @@ function map(callable $func = null): Standard
return $pipeline->map($func);
}

function take(iterable $input = null): Standard
function take(iterable $input = null, iterable ...$inputs): Standard
{
return new Standard($input);
$pipeline = new Standard($input);

foreach ($inputs as $input) {
$pipeline->append($input);
}

return $pipeline;
}

function fromArray(array $input): Standard
{
return new Standard($input);
}

/**
* @param mixed ...$values
*/
function fromValues(...$values): Standard
{
return new Standard($values);
}

function zip(iterable $base, iterable ...$inputs): Standard
{
$result = take($base);
Expand Down
178 changes: 178 additions & 0 deletions tests/AppendPrependTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<?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 function count;
use function is_numeric;
use function key;
use const PHP_VERSION_ID;
use PHPUnit\Framework\TestCase;
use function Pipeline\take;

/**
* @covers \Pipeline\Standard
*
* @internal
*/
final class AppendPrependTest extends TestCase
{
private function generateIterableCombinations(array $arrays): iterable
{
yield $arrays;

$iterableInput = $arrays;
$iterableInput[1] = new ArrayIterator($iterableInput[1] ?? []);

yield $iterableInput;

$iterableSubjects = $arrays;

for ($i = 2; $i < count($iterableSubjects); ++$i) {
$iterableSubjects[$i] = new ArrayIterator($iterableSubjects[$i] ?? []);
}

yield $iterableSubjects;
}

public function provideAppendArrays(): iterable
{
yield [[1, 2, 3, 4, 5], [1, 2, 3], [4, 5]];

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

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

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

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

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

yield [['a', 'b'], ['a'], ['discard' => 'b']];

yield [['a' => 'a', 'bb' => 'b'], ['a' => 'a'], ['bb' => 'b']];
}

/**
* @dataProvider provideAppendArrays
*/
public function testPush(array $expected, ?array $initialValue, ...$iterables): void
{
$useKeys = !is_numeric(key($expected));
if ($useKeys && PHP_VERSION_ID < 80000) {
$this->markTestIncomplete('PHP 7 fails with an error: Cannot unpack array with string keys');
}

$pipeline = take($initialValue);

foreach ($iterables as $iterable) {
$pipeline->push(...$iterable ?? []);
}

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

public function provideAppend(): iterable
{
foreach ($this->provideAppendArrays() as $arrays) {
foreach ($this->generateIterableCombinations($arrays) as $sample) {
yield $sample;
}
}
}

/**
* @dataProvider provideAppend
*/
public function testAppend(array $expected, ?iterable $initialValue, ...$iterables): void
{
$pipeline = take($initialValue);

foreach ($iterables as $iterable) {
$pipeline->append($iterable);
}

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

public function providePrependArrays(): iterable
{
yield [[1, 2, 3, 4, 5], [4, 5], [1, 2, 3]];

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

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

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

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

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

yield [['b', 'a'], ['a'], ['discard' => 'b']];

yield [['bb' => 'b', 'a' => 'a'], ['a' => 'a'], ['bb' => 'b']];
}

/**
* @dataProvider providePrependArrays
*/
public function testUnshift(array $expected, ?array $initialValue, ...$iterables): void
{
$useKeys = !is_numeric(key($expected));
if ($useKeys && PHP_VERSION_ID < 80000) {
$this->markTestIncomplete('PHP 7 fails with an error: Cannot unpack array with string keys');
}

$pipeline = take($initialValue);

foreach ($iterables as $iterable) {
$pipeline->unshift(...$iterable ?? []);
}

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

public function providePrepend(): iterable
{
foreach ($this->providePrependArrays() as $arrays) {
foreach ($this->generateIterableCombinations($arrays) as $sample) {
yield $sample;
}
}
}

/**
* @dataProvider providePrepend
*/
public function testPrepend(array $expected, ?iterable $initialValue, ...$iterables): void
{
$pipeline = take($initialValue);

foreach ($iterables as $iterable) {
$pipeline->prepend($iterable);
}

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

0 comments on commit a7045e3

Please sign in to comment.