Skip to content

Commit

Permalink
Update Span and Partition operations. (#148)
Browse files Browse the repository at this point in the history
* show partition failure

(cherry picked from commit 9eaf340)

* Another example

(cherry picked from commit 5afe280)

* Fix bug with Partition operation.

* Update Span operation.

* Fix static analysis (this was painful)

* Update docs

* Fix bad copy-paste

* Add a note and TODO about phpstan/phpstan#3770.

Co-authored-by: AlexandruGG <alex.gidei@goodlord.co>
  • Loading branch information
drupol and AlexandruGG authored Jul 17, 2021
1 parent 9038c00 commit a531675
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 51 deletions.
11 changes: 9 additions & 2 deletions docs/pages/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ fromCallable

Create a collection from a callable.

Signature: ``Collection::fromCallable(callable $callable, ...$parameters): Collection;``
.. tip:: This can be very useful when working with a PHP `Generator`_, since it will allow the collection
object to behave as if the Generator was rewindable.

Signature: ``Collection::fromCallable(callable $callable, iterable $parameters = []): Collection;``

.. code-block:: php
$callback = static function () {
$callback = static function (): Generator {
yield 'a';
yield 'b';
yield 'c';
Expand All @@ -50,6 +53,10 @@ fromIterable

Create a collection from an iterable.

.. warning:: When instantiating from a PHP `Generator`_, the collection object will inherit its behaviour:
it will only be iterable a single time, and an exception will be thrown if multiple operations which attempt
to re-iterate are applied, for example ``count()``.

Signature: ``Collection::fromIterable(iterable $iterable): Collection;``

.. code-block:: php
Expand Down
48 changes: 30 additions & 18 deletions spec/loophp/collection/CollectionSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -2262,36 +2262,48 @@ public function it_can_partition(): void

$input = array_combine(range('a', 'l'), [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3]);

// Using `first` and `last`, single callback

$subject = $this::fromIterable($input)->partition($isGreaterThan(5));
$subject->shouldHaveCount(2);
$subject->first()->shouldBeAnInstanceOf(CollectionInterface::class);
$subject->last()->shouldBeAnInstanceOf(CollectionInterface::class);

$first = $subject->first()->current();
$last = $subject->last()->current();

$first->shouldBeAnInstanceOf(CollectionInterface::class);
$last->shouldBeAnInstanceOf(CollectionInterface::class);

$first->shouldHaveCount(4);
$last->shouldHaveCount(8);

$first->shouldIterateAs(['f' => 6, 'g' => 7, 'h' => 8, 'i' => 9]);
$last->shouldIterateAs(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'j' => 1, 'k' => 2, 'l' => 3]);

// Using `all` and array destructuring, single callback

[$passed, $rejected] = $this::fromIterable($input)->partition($isGreaterThan(5))->all();
$passed->shouldBeAnInstanceOf(CollectionInterface::class);
$rejected->shouldBeAnInstanceOf(CollectionInterface::class);

$passed->shouldHaveCount(4);
$rejected->shouldHaveCount(8);

$passed->shouldIterateAs(['f' => 6, 'g' => 7, 'h' => 8, 'i' => 9]);
$rejected->shouldIterateAs(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'j' => 1, 'k' => 2, 'l' => 3]);

// Using multiple callbacks

$this::fromIterable($input)
->partition($isGreaterThan(5), $isGreaterThan(3))
->first()
->current()
->shouldIterateAs([
'd' => 4,
'e' => 5,
'f' => 6,
'g' => 7,
'h' => 8,
'i' => 9,
]);
->shouldIterateAs(['d' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9]);

$this::fromIterable($input)
->partition($isGreaterThan(5), $isGreaterThan(3))
->last()
->current()
->shouldIterateAs([
'a' => 1,
'b' => 2,
'c' => 3,
'j' => 1,
'k' => 2,
'l' => 3,
]);
->shouldIterateAs(['a' => 1, 'b' => 2, 'c' => 3, 'j' => 1, 'k' => 2, 'l' => 3]);
}

public function it_can_permutate(): void
Expand Down
39 changes: 25 additions & 14 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use Closure;
use Doctrine\Common\Collections\Criteria;
use Generator;
use Iterator;
use loophp\collection\Contract\Collection as CollectionInterface;
use loophp\collection\Contract\Operation;
Expand Down Expand Up @@ -666,13 +667,18 @@ public function pair(): CollectionInterface

public function partition(callable ...$callbacks): CollectionInterface
{
return new self(
Pipe::of()(
Partition::of()(...$callbacks),
Map::of()(static fn (Iterator $iterator): CollectionInterface => self::fromIterable($iterator))
),
[$this->getIterator()]
);
// TODO: Move this docblock above closure when https://github.com/phpstan/phpstan/issues/3770 lands.
$mapCallback = static function (array $partitionResult): CollectionInterface {
/**
* @var Closure(Iterator<TKey, T>): Generator<TKey, T> $callback
* @var array{0: Iterator<TKey, T>} $parameters
*/
[$callback, $parameters] = $partitionResult;

return self::fromCallable($callback, $parameters);
};

return new self(Pipe::of()(Partition::of()(...$callbacks), Map::of()($mapCallback)), [$this->getIterator()]);
}

public function permutate(): CollectionInterface
Expand Down Expand Up @@ -782,13 +788,18 @@ public function sort(int $type = Operation\Sortable::BY_VALUES, ?callable $callb

public function span(callable ...$callbacks): CollectionInterface
{
return new self(
Pipe::of()(
Span::of()(...$callbacks),
Map::of()(static fn (Iterator $iterator): CollectionInterface => self::fromIterable($iterator))
),
[$this->getIterator()]
);
// TODO: Move this docblock above closure when https://github.com/phpstan/phpstan/issues/3770 lands.
$mapCallback = static function (array $spanResult): CollectionInterface {
/**
* @var Closure(Iterator<TKey, T>): Generator<TKey, T> $callback
* @var array{0: Iterator<TKey, T>} $parameters
*/
[$callback, $parameters] = $spanResult;

return self::fromCallable($callback, $parameters);
};

return new self(Pipe::of()(Span::of()(...$callbacks), Map::of()($mapCallback)), [$this->getIterator()]);
}

public function split(int $type = Operation\Splitable::BEFORE, callable ...$callbacks): CollectionInterface
Expand Down
19 changes: 10 additions & 9 deletions src/Operation/Partition.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,30 @@ final class Partition extends AbstractOperation
/**
* @pure
*
* @return Closure(callable(T, TKey, Iterator<TKey, T>): bool...): Closure(Iterator<TKey, T>): Generator<int, Iterator<TKey, T>>
* @return Closure(callable(T, TKey, Iterator<TKey, T>): bool ...): Closure(Iterator<TKey, T>): Generator<int, array{0: Closure(Iterator<TKey, T>): Generator<TKey, T>, 1: array{0: Iterator<TKey, T>}}>
*/
public function __invoke(): Closure
{
return
/**
* @param callable(T, TKey, Iterator<TKey, T>): bool ...$callbacks
*
* @return Closure(Iterator<TKey, T>): Generator<int, Iterator<TKey, T>>
* @return Closure(Iterator<TKey, T>): Generator<int, array{0: Closure(Iterator<TKey, T>): Generator<TKey, T>, 1: array{0: Iterator<TKey, T>}}>
*/
static fn (callable ...$callbacks): Closure =>
/**
* @param Iterator<TKey, T> $iterator
*
* @return Generator<int, Iterator<TKey, T>>
* @return Generator<int, array{0: Closure(Iterator<TKey, T>): Generator<TKey, T>, 1: array{0: Iterator<TKey, T>}}>
*/
static function (Iterator $iterator) use ($callbacks): Iterator {
/** @var Iterator<TKey, T> $filter */
$filter = Filter::of()(...$callbacks)($iterator);
/** @var Iterator<TKey, T> $reject */
$reject = Reject::of()(...$callbacks)($iterator);
static function (Iterator $iterator) use ($callbacks): Generator {
/** @var array{0: Closure(Iterator<TKey, T>): Generator<TKey, T>, 1: array{0: Iterator<TKey, T>}} $filter */
$filter = [Filter::of()(...$callbacks), [$iterator]];

return yield from [$filter, $reject];
/** @var array{0: Closure(Iterator<TKey, T>): Generator<TKey, T>, 1: array{0: Iterator<TKey, T>}} $reject */
$reject = [Reject::of()(...$callbacks), [$iterator]];

yield from [$filter, $reject];
};
}
}
17 changes: 9 additions & 8 deletions src/Operation/Span.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,30 @@ final class Span extends AbstractOperation
/**
* @pure
*
* @return Closure(callable(T, TKey, Iterator<TKey, T>): bool): Closure(Iterator<TKey, T>): Generator<int, Iterator<TKey, T>>
* @return Closure(callable(T, TKey, Iterator<TKey, T>): bool ...): Closure(Iterator<TKey, T>): Generator<int, array{0: Closure(Iterator<TKey, T>): Generator<TKey, T>, 1: array{0: Iterator<TKey, T>}}>
*/
public function __invoke(): Closure
{
return
/**
* @param callable(T, TKey, Iterator<TKey, T>): bool ...$callbacks
*
* @return Closure(Iterator<TKey, T>): Generator<int, Iterator<TKey, T>>
* @return Closure(Iterator<TKey, T>): Generator<int, array{0: Closure(Iterator<TKey, T>): Generator<TKey, T>, 1: array{0: Iterator<TKey, T>}}>
*/
static fn (callable ...$callbacks): Closure =>
/**
* @param Iterator<TKey, T> $iterator
*
* @return Generator<int, Iterator<TKey, T>>
* @return Generator<int, array{0: Closure(Iterator<TKey, T>): Generator<TKey, T>, 1: array{0: Iterator<TKey, T>}}>
*/
static function (Iterator $iterator) use ($callbacks): Generator {
/** @var Iterator<TKey, T> $takeWhile */
$takeWhile = TakeWhile::of()(...$callbacks)($iterator);
/** @var Iterator<TKey, T> $dropWhile */
$dropWhile = DropWhile::of()(...$callbacks)($iterator);
/** @var array{0: Closure(Iterator<TKey, T>): Generator<TKey, T>, 1: array{0: Iterator<TKey, T>}} $takeWhile */
$takeWhile = [TakeWhile::of()(...$callbacks), [$iterator]];

return yield from [$takeWhile, $dropWhile];
/** @var array{0: Closure(Iterator<TKey, T>): Generator<TKey, T>, 1: array{0: Iterator<TKey, T>}} $dropWhile */
$dropWhile = [DropWhile::of()(...$callbacks), [$iterator]];

yield from [$takeWhile, $dropWhile];
};
}
}

0 comments on commit a531675

Please sign in to comment.