Skip to content

Commit

Permalink
refactor: Update Split operation.
Browse files Browse the repository at this point in the history
Provide a new parameter that let users specific what to do with the
collection value used to explode the collection into chunks. Insert it
before, after or completely remove it.

BREAKING CHANGE: yes
  • Loading branch information
drupol committed Sep 21, 2020
1 parent 0835f2d commit 1c669b2
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 72 deletions.
22 changes: 17 additions & 5 deletions docs/pages/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,9 @@ explode

Explode a collection into subsets based on a given value.

This operation use the Split operation with the flag ``Splitable::REMOVE`` and thus, values used to explode the
collection are removed from the chunks.

Interface: `Explodeable`_

Signature: ``Collection::explode(...$items);``
Expand Down Expand Up @@ -1511,20 +1514,29 @@ Signature: ``Collection::sort(?callable $callback = null);``
split
~~~~~

Split a collection using a callback.
Split a collection using one or more callbacks.

A flag must be provided in order to specify whether the value used to split the collection should be added at the end
of a chunk, at the beginning of a chunk, or completely removed.

Interface: `Splitable`_

Signature: ``Collection::split(callable ...$callbacks);``
Signature: ``Collection::split(int $type = Splitable::BEFORE, callable ...$callbacks);``

.. code-block:: php
$splitter = static function ($value, $key) {
$splitter = static function ($value): bool {
return 0 === $value % 3;
};
$collection = Collection::fromIterable(range(0, 20))
->split($splitter);
$collection = Collection::fromIterable(range(0, 10))
->split(Splitable::BEFORE, $splitter); [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
$collection = Collection::fromIterable(range(0, 10))
->split(Splitable::AFTER, $splitter); [[0], [1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
$collection = Collection::fromIterable(range(0, 10))
->split(Splitable::REMOVE, $splitter); [[1, 2], [4, 5], [7, 8], [10]]
tail
~~~~
Expand Down
36 changes: 24 additions & 12 deletions spec/loophp/collection/CollectionSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -1911,18 +1911,30 @@ static function ($left, $right): int {

public function it_can_split(): void
{
$this::fromIterable(range(1, 17))
->split(static function ($value) {
return 0 === $value % 3;
})
->shouldIterateAs([
0 => [1, 2],
1 => [3, 4, 5],
2 => [6, 7, 8],
3 => [9, 10, 11],
4 => [12, 13, 14],
5 => [15, 16, 17],
]);
$splitter = static function ($value): bool {
return 0 === $value % 3;
};

$this::fromIterable(range(0, 10))
->split(
Operation\Splitable::BEFORE,
$splitter
)
->shouldIterateAs([[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]);

$this::fromIterable(range(0, 10))
->split(
Operation\Splitable::REMOVE,
$splitter
)
->shouldIterateAs([[], [1, 2], [4, 5], [7, 8], [10]]);

$this::fromIterable(range(0, 10))
->split(
Operation\Splitable::AFTER,
$splitter
)
->shouldIterateAs([[0], [1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]);
}

public function it_can_tail(): void
Expand Down
4 changes: 2 additions & 2 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -720,9 +720,9 @@ public function sort(int $type = Operation\Sortable::BY_VALUES, ?callable $callb
return $this->run(Sort::of()($type)($callback));
}

public function split(callable ...$callbacks): CollectionInterface
public function split(int $type = Operation\Splitable::BEFORE, callable ...$callbacks): CollectionInterface
{
return $this->run(Split::of()(...$callbacks));
return $this->run(Split::of()($type)(...$callbacks));
}

public function tail(): CollectionInterface
Expand Down
8 changes: 7 additions & 1 deletion src/Contract/Operation/Splitable.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@
*/
interface Splitable
{
public const AFTER = 1;

public const BEFORE = -1;

public const REMOVE = 0;

/**
* Split a collection using a callback.
*
* @param callable ...$callbacks
*
* @psalm-return \loophp\collection\Contract\Collection<TKey, T>
*/
public function split(callable ...$callbacks): Collection;
public function split(int $type = Splitable::BEFORE, callable ...$callbacks): Collection;
}
127 changes: 75 additions & 52 deletions src/Operation/Split.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,80 +7,103 @@
use Closure;
use Generator;
use Iterator;
use loophp\collection\Contract\Operation\Splitable;

/**
* @psalm-template TKey
* @psalm-template TKey of array-key
* @psalm-template T
*
* phpcs:disable Generic.Files.LineLength.TooLong
* phpcs:disable Generic.WhiteSpace.ScopeIndent.IncorrectExact
*/
final class Split extends AbstractOperation
{
/**
* @psalm-return Closure((callable(T, TKey): bool)...): Closure(Iterator<TKey, T>): Generator<int, list<T>>
* @psalm-return Closure(int): Closure((callable(T, TKey): bool)...): Closure(Iterator<TKey, T>): Generator<int, list<T>>
*/
public function __invoke(): Closure
{
return
/**
* @psalm-param callable(T, TKey): bool ...$callbacks
*
* @psalm-return Closure(Iterator<TKey, T>): Generator<int, list<T>>
* @psalm-return Closure((callable(T, TKey): bool)...): Closure(Iterator<TKey, T>): Generator<int, list<T>>
*/
static function (callable ...$callbacks): Closure {
static function (int $type = Splitable::BEFORE): Closure {
return
/**
* @psalm-param Iterator<TKey, T> $iterator
* @psalm-param list<callable(T, TKey):(bool)> $callbacks
*
* @psalm-return Generator<int, list<T>>
*/
static function (Iterator $iterator) use ($callbacks): Generator {
$carry = [];
/**
* @psalm-param callable(T, TKey): bool ...$callbacks
*
* @psalm-return Closure(Iterator<TKey, T>): Generator<int, list<T>>
*/
static function (callable ...$callbacks) use ($type): Closure {
return
/**
* @psalm-param Iterator<TKey, T> $iterator
*
* @psalm-return Generator<int, list<T>>
*/
static function (Iterator $iterator) use ($type, $callbacks): Generator {
$carry = [];

$reducer =
/**
* @psalm-param TKey $key
*
* @psalm-return Closure(T): Closure(bool, callable(T, TKey): bool): bool
*
* @param mixed $key
*/
static function ($key): Closure {
return
/**
* @psalm-param T $value
*
* @psalm-return Closure(bool, callable(T, TKey): bool): bool
*
* @param mixed $value
*/
static function ($value) use ($key): Closure {
return
/**
* @psalm-param callable(T, TKey): bool $callback
*/
static function (bool $carry, callable $callback) use ($key, $value): bool {
return $callback($value, $key) !== $carry;
};
};
};
$reducer =
/**
* @psalm-param TKey $key
*
* @psalm-return Closure(T): Closure(bool, callable(T, TKey): bool): bool
*
* @param mixed $key
*/
static function ($key): Closure {
return
/**
* @psalm-param T $value
*
* @psalm-return Closure(bool, callable(T, TKey): bool): bool
*
* @param mixed $value
*/
static function ($value) use ($key): Closure {
return
/**
* @psalm-param callable(T, TKey): bool $callback
*/
static function (bool $carry, callable $callback) use ($key, $value): bool {
return $callback($value, $key) || $carry;
};
};
};

foreach ($iterator as $key => $value) {
$callbackReturn = array_reduce($callbacks, $reducer($key)($value), false);
foreach ($iterator as $key => $value) {
$callbackReturn = array_reduce($callbacks, $reducer($key)($value), false);

if (true === $callbackReturn && [] !== $carry) {
yield $carry;
if (Splitable::AFTER === $type) {
$carry[] = $value;
}

$carry = [];
}
if (Splitable::REMOVE === $type && true === $callbackReturn) {
yield $carry;

$carry[] = $value;
}
$carry = [];

if ([] !== $carry) {
yield $carry;
}
};
continue;
}

if (true === $callbackReturn && [] !== $carry) {
yield $carry;

$carry = [];
}

if (Splitable::BEFORE === $type || Splitable::REMOVE === $type) {
$carry[] = $value;
}
}

if ([] !== $carry) {
yield $carry;
}
};
};
};
}
}

0 comments on commit 1c669b2

Please sign in to comment.