Skip to content

Commit

Permalink
Refactor ::partition() and ::span() (#124)
Browse files Browse the repository at this point in the history
* Update Partition operation.

* Update Span operation.

* Update tests.

* Update documentation.

* Update documentation

* Improve tests

* SA Check: Span

* SA Check: Partition

Co-authored-by: AlexandruGG <alex.gidei@goodlord.co>
  • Loading branch information
drupol and AlexandruGG committed Jul 8, 2021
1 parent 30e53b0 commit 3543001
Show file tree
Hide file tree
Showing 11 changed files with 345 additions and 119 deletions.
36 changes: 27 additions & 9 deletions docs/pages/api.rst
Expand Up @@ -1524,12 +1524,25 @@ Signature: ``Collection::pair();``
partition
~~~~~~~~~

With one or multiple callable, partition the items into 2 subgroups of items.
Partition the collection into two subgroups of items using one or more callables.

.. warning:: The `callbacks` parameter is variadic and will be evaluated as a logical ``OR``.
If you're looking for a logical ``AND``, you have to make multiple calls to the
same operation.

The raw ``Partition`` operation returns a generator yielding two iterators.

The first inner iterator is the result of a ``filter`` operation, it contains items
that have met the provided callback(s).
The second (and last) inner iterator is the result of a ``reject`` operation, it contains items
that have not met the provided callback(s).

When the ``partition`` operation is used through the ``Collection`` object, the two
resulting iterators will be converted and mapped into a ``Collection`` object.

The first inner collection contains items that have met the provided callback(s).
The second (and last) collection contains items that have not met the provided callback(s).

Interface: `Partitionable`_

Signature: ``Collection::partition(callable ...$callbacks);``
Expand Down Expand Up @@ -1913,19 +1926,24 @@ Signature: ``Collection::sort(?callable $callback = null);``
span
~~~~

Returns a tuple where the first element is the longest prefix (possibly empty) of elements
that satisfy the callback and the second element is the remainder.
Partition the collection into two subgroups where the first element is the longest
prefix (*possibly empty*) of elements that satisfy the callback and the second element
is the remainder.

Interface: `Spanable`_
The raw ``Span`` operation returns a generator yielding two iterators.

Signature: ``Collection::span(callable $callback);``
The first inner iterator is the result of a ``TakeWhile`` operation.
The second (and last) inner iterator is the result of a ``DropWhile`` operation.

.. code-block:: php
When the ``span`` operation is used through the ``Collection`` object, the two
resulting iterators will be converted and mapped into ``Collection`` objects.

$input = range(1, 10);
Interface: `Spanable`_

Collection::fromIterable($input)
->span(fn ($x) => $x < 4); // [[1, 2, 3], [4, 5, 6, 7, 8, 9, 10]]
Signature: ``Collection::span(callable $callback);``

.. literalinclude:: code/operations/span.php
:language: php

split
~~~~~
Expand Down
80 changes: 58 additions & 22 deletions docs/pages/code/operations/partition.php
Expand Up @@ -9,39 +9,75 @@

namespace App;

use ArrayIterator;
use Closure;
use loophp\collection\Collection;
use loophp\collection\Operation\Partition;

include __DIR__ . '/../../../../vendor/autoload.php';

$isGreaterThan = static fn (int $left): Closure => static fn (int $right): bool => $left < $right;

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

$collection = Collection::fromIterable($input)
->partition(
$isGreaterThan(5),
$isGreaterThan(3)
);
// Example 1 -> Retrieve the left and right groups
[$left, $right] = Collection::fromIterable($input)
->partition($isGreaterThan(5))
->all();

// Result
// Numbers that are greater than 5
print_r($left->all());
/*
[
[
['d', 4],
['e', 5],
['f', 6],
['g', 7],
['h', 8],
['i', 9],
],
[
['a', 1],
['b', 2],
['c', 3],
['j', 1],
['k', 2],
['l', 3],
],
['f', 6],
['g', 7],
['h', 8],
['i', 9],
]
*/

// Numbers that are not greater than 5
print_r($right->all());
/*
[
['a', 1],
['b', 2],
['c', 3],
['d', 4],
['e', 5],
['j', 1],
['k', 2],
['l', 3],
]
*/

// Example 2 -> Retrieve the first group only
$left = Collection::fromIterable($input)
->partition($isGreaterThan(5))
->first()
->current();

// Numbers that are greater than 5
print_r($left->all());
/*
[
['f', 6],
['g', 7],
['h', 8],
['i', 9],
]
*/

// Example 3 -> Use Partition operation separately
[$left] = iterator_to_array(Partition::of()($isGreaterThan(5))(new ArrayIterator($input)));

// Numbers that are greater than 5
print_r(iterator_to_array($left));
/*
[
['f', 6],
['g', 7],
['h', 8],
['i', 9],
]
*/
39 changes: 39 additions & 0 deletions docs/pages/code/operations/span.php
@@ -0,0 +1,39 @@
<?php

/**
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

declare(strict_types=1);

namespace App;

use ArrayIterator;
use loophp\collection\Collection;
use loophp\collection\Operation\Span;

include __DIR__ . '/../../../../vendor/autoload.php';

$input = range(1, 10);

// Example 1 -> Retrieve the left and right groups
[$first, $last] = Collection::fromIterable($input)
->span(static fn ($x): bool => 4 > $x)
->all();

print_r($first->all()); // [1, 2, 3]
print_r($last->all()); // [4, 5, 6, 7, 8, 9, 10]

// Example 2 -> Retrieve the second group only
$last = Collection::fromIterable($input)
->span(static fn ($x): bool => 4 > $x)
->last()
->current();

print_r($last->all()); // [4, 5, 6, 7, 8, 9, 10]

// Example 3 -> Use Span operation separately
[$left] = iterator_to_array(Span::of()(static fn ($x): bool => 4 > $x)(new ArrayIterator($input)));

print_r(iterator_to_array($left)); // [1, 2, 3]
69 changes: 34 additions & 35 deletions spec/loophp/collection/CollectionSpec.php
Expand Up @@ -19,6 +19,7 @@
use Iterator;
use JsonSerializable;
use loophp\collection\Collection;
use loophp\collection\Contract\Collection as CollectionInterface;
use loophp\collection\Contract\Operation;
use loophp\collection\Operation\AbstractOperation;
use OutOfBoundsException;
Expand Down Expand Up @@ -2252,28 +2253,35 @@ public function it_can_partition(): void

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

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

$this::fromIterable($input)
->partition(
$isGreaterThan(5),
$isGreaterThan(3)
)
->partition($isGreaterThan(5), $isGreaterThan(3))
->first()
->current()
->shouldIterateAs([
[
['d', 4],
['e', 5],
['f', 6],
['g', 7],
['h', 8],
['i', 9],
],
[
['a', 1],
['b', 2],
['c', 3],
['j', 1],
['k', 2],
['l', 3],
],
'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,
]);
}

Expand Down Expand Up @@ -2900,22 +2908,13 @@ public function it_can_span(): void
{
$input = range(1, 10);

$test = $this::fromIterable($input)
->span(static function (int $x): bool {return 4 > $x; });
$subject = $this::fromIterable($input)->span(static fn (int $x): bool => 4 > $x);
$subject->shouldHaveCount(2);
$subject->first()->shouldBeAnInstanceOf(CollectionInterface::class);
$subject->last()->shouldBeAnInstanceOf(CollectionInterface::class);

$test
->first()
->current()
->shouldIterateAs(
[1, 2, 3]
);

$test
->last()
->current()
->shouldIterateAs(
[3 => 4, 4 => 5, 5 => 6, 6 => 7, 7 => 8, 8 => 9, 9 => 10]
);
$subject->first()->current()->shouldIterateAs([1, 2, 3]);
$subject->last()->current()->shouldIterateAs([3 => 4, 4 => 5, 5 => 6, 6 => 7, 7 => 8, 8 => 9, 9 => 10]);
}

public function it_can_split(): void
Expand Down
16 changes: 14 additions & 2 deletions src/Collection.php
Expand Up @@ -657,7 +657,13 @@ public function pair(): CollectionInterface

public function partition(callable ...$callbacks): CollectionInterface
{
return new self(Partition::of()(...$callbacks), $this->getIterator());
return new self(
Pipe::of()(
Partition::of()(...$callbacks),
Map::of()(static fn (Iterator $iterator): CollectionInterface => self::fromIterable($iterator))
),
$this->getIterator()
);
}

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

public function span(callable $callback): CollectionInterface
{
return new self(Span::of()($callback), $this->getIterator());
return new self(
Pipe::of()(
Span::of()($callback),
Map::of()(static fn (Iterator $iterator): CollectionInterface => self::fromIterable($iterator))
),
$this->getIterator()
);
}

public function split(int $type = Operation\Splitable::BEFORE, callable ...$callbacks): CollectionInterface
Expand Down
5 changes: 3 additions & 2 deletions src/Contract/Operation/Partitionable.php
Expand Up @@ -9,6 +9,7 @@

namespace loophp\collection\Contract\Operation;

use Iterator;
use loophp\collection\Contract\Collection;

/**
Expand All @@ -18,9 +19,9 @@
interface Partitionable
{
/**
* @param callable(T, TKey):bool ...$callbacks
* @param callable(T, TKey, Iterator<TKey, T>): bool ...$callbacks
*
* @return Collection<int, array<int, array{0: TKey, 1: T}>>
* @return Collection<int, Collection<TKey, T>>
*/
public function partition(callable ...$callbacks): Collection;
}
5 changes: 4 additions & 1 deletion src/Contract/Operation/Spanable.php
Expand Up @@ -9,6 +9,7 @@

namespace loophp\collection\Contract\Operation;

use Iterator;
use loophp\collection\Contract\Collection;

/**
Expand All @@ -18,7 +19,9 @@
interface Spanable
{
/**
* @return Collection<TKey, T>
* @param callable(T, TKey, Iterator<TKey, T>): bool $callback
*
* @return Collection<int, Collection<TKey, T>>
*/
public function span(callable $callback): Collection;
}

0 comments on commit 3543001

Please sign in to comment.