Skip to content

Commit

Permalink
Introduce append(), prepend(), push(), unshift() (#108)
Browse files Browse the repository at this point in the history
Fixes #103
Fixes #107
Fixes #106
  • Loading branch information
sanmai authored Nov 9, 2022
1 parent d64f62c commit 1c14946
Show file tree
Hide file tree
Showing 8 changed files with 362 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jobs:
runs-on: ubuntu-latest

env:
PHP_VERSION: 8.0
PHP_VERSION: '8.1'
COMPOSER_ROOT_VERSION: v5.99

steps:
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ COMPOSER=$(PHP) $(shell which composer)

# Infection
INFECTION=vendor/bin/infection
MIN_MSI=100
MIN_COVERED_MSI=100
MIN_MSI=90
MIN_COVERED_MSI=90
INFECTION_ARGS=--min-msi=$(MIN_MSI) --min-covered-msi=$(MIN_COVERED_MSI) --threads=$(JOBS) --coverage=build/logs --log-verbosity=default --show-mutations --no-interaction

all: test
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ All entry points always return an instance of the pipeline.
| Method | Details | Use with |
| ----------- | ----------------------------- | ----------- |
| `map()` | Takes an optional initial callback, where it must not require any arguments. Other than that, works just like an instance method below. | `use function Pipeline\map;` |
| `take()` | Takes any iterable, including arrays, initializes a pipeline with it. | `use function Pipeline\take;` |
| `take()` | Takes any iterables, including arrays, joining them together in succession. | `use function Pipeline\take;` |
| `fromArray()` | Takes an array, initializes a pipeline with it. | `use function Pipeline\fromArray;` |
| `zip()` | Takes an iterable, and several more, merging them together. | `use function Pipeline\zip;` |

Expand All @@ -108,6 +108,10 @@ All entry points always return an instance of the pipeline.
| ----------- | ----------------------------- | ----------------- |
| `map()` | Takes an optional callback that for each input value may return one or yield many. Also takes an initial generator, where it must not require any arguments. Provided no callback does nothing. Also available as a plain function. | `SelectMany` |
| `cast()` | Takes a callback that for each input value expected to return another single value. Unlike `map()`, it assumes no special treatment for generators. Provided no callback does nothing. | `array_map`, `Select` |
| `append()` | Appends the contents of an interable to the end of the pipeline. | `array_merge` |
| `push()` | Appends the arguments to the end of the pipeline. | `array_push` |
| `prepend()` | Appends the contents of an interable to the end of the pipeline. | `array_merge` |
| `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` |
| `filter()` | Removes elements unless a callback returns true. Removes falsey values if no callback provided. | `array_filter`, `Where` |
Expand Down
141 changes: 130 additions & 11 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,13 +74,129 @@ 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, array-optimized.
*
* 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 = self::joinYield($left, $right);

return $this;
}

/**
* Replace the internal pipeline with a combination of two non-empty iterables, generator-way.
*/
private static function joinYield(iterable $left, iterable $right): iterable
{
yield from $left;
yield from $right;
}

/**
* An extra variant of `map` which unpacks arrays into arguments. Flattens inputs if no callback provided.
*
* @param ?callable $func
*
* @psalm-suppress InvalidArgument
*
* @return $this
*/
public function unpack(?callable $func = null): self
Expand All @@ -88,6 +206,7 @@ public function unpack(?callable $func = null): self
};

return $this->map(static function (iterable $args = []) use ($func) {
/** @psalm-suppress InvalidArgument */
return $func(...$args);
});
}
Expand All @@ -109,7 +228,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 +296,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 @@ -231,11 +348,10 @@ public function filter(?callable $func = null): self
return $this;
}

/** @var Iterator $iterator */
$iterator = $this->pipeline;
assert($this->pipeline instanceof Iterator);

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

return $this;
}
Expand Down Expand Up @@ -390,9 +506,12 @@ private static function makeNonRewindable(iterable $input): Generator
return $input;
}

return (static function (iterable $input) {
yield from $input;
})($input);
return self::generatorFromIterable($input);
}

private static function generatorFromIterable(iterable $input): Generator
{
yield from $input;
}

/**
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
Loading

0 comments on commit 1c14946

Please sign in to comment.