Skip to content

Commit

Permalink
Add Reduce operation (#139)
Browse files Browse the repository at this point in the history
* feat: Add Reduce operation.

* tests: Add tests.

* docs: Add documentation.

* refactor: Use Reduce operation in Reverse operation.

* refactor: Use Reduce instead of FoldLeft for better performance.

* Minor additions

Co-authored-by: AlexandruGG <alex.gidei@goodlord.co>
  • Loading branch information
drupol and AlexandruGG authored Jul 19, 2021
1 parent 404d0b7 commit 18a68c4
Show file tree
Hide file tree
Showing 16 changed files with 246 additions and 19 deletions.
20 changes: 17 additions & 3 deletions docs/pages/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ When used as a ``Collection`` method, operations fall into a few main categories

4. Operations that return a new ``Collection`` object: all other operations.

.. note:: The ``Key`` operation can return any value because ``Collection`` leverages PHP Generators,
.. note:: The ``Key`` operation can return any value because ``Collection`` leverages PHP Generators,
which allow using any type as a key as opposed to ``array``, which only allows ``int|string`` keys.

.. note:: Earlier versions of the package had most operations returning a new ``Collection`` object.
Expand Down Expand Up @@ -1604,7 +1604,7 @@ Partition the collection into two subgroups of items using one or more callables

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

The first inner iterator is the result of a ``filter`` operation, it contains items
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).
Expand Down Expand Up @@ -1751,10 +1751,23 @@ Signature: ``Collection::random(int $size = 1, ?int $seed = null): Collection;``
$collection = Collection::fromIterable(['4', '5', '6'])
->random(); // ['6']
reduce
~~~~~~

Reduce a collection of items through a given callback.

Interface: `Reduceable`_

Signature: ``Collection::reduce(callable $callback, $initial = null): Collection;``

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

reduction
~~~~~~~~~

Reduce a collection of items through a given callback.
Reduce a collection of items through a given callback and yield
each intermediary results.

Interface: `Reductionable`_

Expand Down Expand Up @@ -2509,6 +2522,7 @@ Signature: ``Collection::zip(iterable ...$iterables): Collection;``
.. _Prependable: https://github.com/loophp/collection/blob/master/src/Contract/Operation/Prependable.php
.. _Productable: https://github.com/loophp/collection/blob/master/src/Contract/Operation/Productable.php
.. _Randomable: https://github.com/loophp/collection/blob/master/src/Contract/Operation/Randomable.php
.. _Reduceable: https://github.com/loophp/collection/blob/master/src/Contract/Operation/Reduceable.php
.. _Reductionable: https://github.com/loophp/collection/blob/master/src/Contract/Operation/Reductionable.php
.. _Rejectable: https://github.com/loophp/collection/blob/master/src/Contract/Operation/Rejectable.php
.. _Reverseable: https://github.com/loophp/collection/blob/master/src/Contract/Operation/Reverseable.php
Expand Down
22 changes: 22 additions & 0 deletions docs/pages/code/operations/reduce.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?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 loophp\collection\Collection;

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

$callback = static fn (int $carry, int $item): int => $carry + $item;

$collection = Collection::empty()
->reduce($callback); // []

$collection = Collection::fromIterable(range(1, 5))
->reduce($callback, 0); // [4 => 15]
27 changes: 27 additions & 0 deletions spec/loophp/collection/CollectionSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -1425,6 +1425,10 @@ public function it_can_flip(): void

public function it_can_fold_from_the_left(): void
{
$this::empty()
->foldLeft(static fn (string $carry, string $string): string => sprintf('%s%s', $carry, $string), 'foo')
->shouldIterateAs(['foo']);

$this::fromIterable(range('A', 'C'))
->foldLeft(
static function (string $carry, string $item): string {
Expand Down Expand Up @@ -2650,6 +2654,29 @@ public function it_can_random(): void
->during('all');
}

public function it_can_reduce(): void
{
$this::empty()
->reduce(static fn (string $carry, string $string): string => sprintf('%s%s', $carry, $string), 'foo')
->shouldIterateAs([]);

$this::fromIterable(range(1, 5))
->reduce(
static fn (int $carry, int $item): int => $carry + $item,
0
)
->shouldIterateAs([4 => 15]);

$this::fromIterable(array_combine(range('x', 'z'), range('a', 'c')))
->reduce(
static fn (string $carry, string $letter, string $index): string => sprintf('%s[%s:%s]', $carry, $index, $letter),
'=> '
)
->shouldIterateAs([
'z' => '=> [x:a][y:b][z:c]',
]);
}

public function it_can_reduction(): void
{
$this::fromIterable(range(1, 5))
Expand Down
6 changes: 6 additions & 0 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
use loophp\collection\Operation\Product;
use loophp\collection\Operation\Random;
use loophp\collection\Operation\Range;
use loophp\collection\Operation\Reduce;
use loophp\collection\Operation\Reduction;
use loophp\collection\Operation\Reject;
use loophp\collection\Operation\Reverse;
Expand Down Expand Up @@ -722,6 +723,11 @@ public static function range(float $start = 0.0, float $end = INF, float $step =
return self::empty()->pipe(Range::of()($start)($end)($step));
}

public function reduce(callable $callback, $initial = null): CollectionInterface
{
return new self(Reduce::of()($callback)($initial), [$this->getIterator()]);
}

public function reduction(callable $callback, $initial = null): CollectionInterface
{
return new self(Reduction::of()($callback)($initial), [$this->getIterator()]);
Expand Down
3 changes: 3 additions & 0 deletions src/Contract/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
use loophp\collection\Contract\Operation\Productable;
use loophp\collection\Contract\Operation\Randomable;
use loophp\collection\Contract\Operation\Rangeable;
use loophp\collection\Contract\Operation\Reduceable;
use loophp\collection\Contract\Operation\Reductionable;
use loophp\collection\Contract\Operation\Rejectable;
use loophp\collection\Contract\Operation\Reverseable;
Expand Down Expand Up @@ -208,6 +209,7 @@
* @template-extends Prependable<TKey, T>
* @template-extends Productable<TKey, T>
* @template-extends Randomable<TKey, T>
* @template-extends Reduceable<TKey, T>
* @template-extends Reductionable<TKey, T>
* @template-extends Rejectable<TKey, T>
* @template-extends Reverseable<TKey, T>
Expand Down Expand Up @@ -325,6 +327,7 @@ interface Collection extends
Productable,
Randomable,
Rangeable,
Reduceable,
Reductionable,
Rejectable,
Reverseable,
Expand Down
32 changes: 32 additions & 0 deletions src/Contract/Operation/Reduceable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?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 loophp\collection\Contract\Operation;

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

/**
* @template TKey
* @template T
*/
interface Reduceable
{
/**
* Reduce a collection of items through a given callback.
*
* @template V
*
* @param callable(V, T, TKey, Iterator<TKey, T>): V $callback
* @param V $initial
*
* @return Collection<TKey, V>
*/
public function reduce(callable $callback, $initial = null): Collection;
}
4 changes: 2 additions & 2 deletions src/Operation/Associate.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ static function (Iterator $iterator) use ($callbackForKeys, $callbackForValues):

foreach ($iterator as $key => $value) {
/** @var Generator<int, T|TKey> $k */
$k = FoldLeft::of()($callbackFactory($key)($value))($key)(new ArrayIterator($callbackForKeys));
$k = Reduce::of()($callbackFactory($key)($value))($key)(new ArrayIterator($callbackForKeys));

/** @var Generator<int, T|TKey> $c */
$c = FoldLeft::of()($callbackFactory($key)($value))($value)(new ArrayIterator($callbackForValues));
$c = Reduce::of()($callbackFactory($key)($value))($value)(new ArrayIterator($callbackForValues));

yield $k->current() => $c->current();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Operation/Distinct.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ static function (array $seen, array $value) use ($accessorCallback, $comparatorC
/** @var Closure(Iterator<TKey, T>): Generator<TKey, T> $pipe */
$pipe = Pipe::of()(
Pack::of(),
FoldLeft::of()($foldLeftCallbackBuilder($accessorCallback)($comparatorCallback))([]),
Reduce::of()($foldLeftCallbackBuilder($accessorCallback)($comparatorCallback))([]),
Flatten::of()(1),
Unpack::of()
);
Expand Down
2 changes: 1 addition & 1 deletion src/Operation/Frequency.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ static function (array $storage, $value): array {

/** @var Closure(Iterator<TKey, T>): Generator<int, T> $pipe */
$pipe = Pipe::of()(
FoldLeft::of()($reduceCallback)([]),
Reduce::of()($reduceCallback)([]),
Flatten::of()(1),
Unpack::of()
);
Expand Down
2 changes: 1 addition & 1 deletion src/Operation/GroupBy.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ static function (array $collect, $value, $key) use ($callback): array {

/** @var Closure(Iterator<TKey, T>): Generator<int, list<T>> $pipe */
$pipe = Pipe::of()(
FoldLeft::of()($reducerFactory($callable))([]),
Reduce::of()($reducerFactory($callable))([]),
Flatten::of()(1)
);

Expand Down
2 changes: 1 addition & 1 deletion src/Operation/Implode.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ static function (string $glue): Closure {
$pipe = Pipe::of()(
Intersperse::of()($glue)(1)(0),
Drop::of()(1),
FoldLeft::of()($reducer)('')
Reduce::of()($reducer)('')
);

// Point free style.
Expand Down
58 changes: 58 additions & 0 deletions src/Operation/Reduce.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?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 loophp\collection\Operation;

use Closure;
use Generator;
use Iterator;

/**
* @immutable
*
* @template TKey
* @template T
*
* phpcs:disable Generic.Files.LineLength.TooLong
*/
final class Reduce extends AbstractOperation
{
/**
* @pure
*
* @template V
*
* @return Closure(callable(V, T, TKey, Iterator<TKey, T>): V): Closure (V): Closure(Iterator<TKey, T>): Generator<TKey, V>
*/
public function __invoke(): Closure
{
return
/**
* @param callable(V, T, TKey, Iterator<TKey, T>): V $callback
*
* @return Closure(V): Closure(Iterator<TKey, T>): Generator<TKey, V>
*/
static fn (callable $callback): Closure =>
/**
* @param V $initial
*
* @return Closure(Iterator<TKey, T>): Generator<TKey, V>
*/
static function ($initial) use ($callback): Closure {
/** @var Closure(Iterator<TKey, T>): Generator<TKey, V> $pipe */
$pipe = Pipe::of()(
Reduction::of()($callback)($initial),
Last::of(),
);

// Point free style.
return $pipe;
};
}
}
16 changes: 9 additions & 7 deletions src/Operation/Reduction.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,29 @@ final class Reduction extends AbstractOperation
/**
* @pure
*
* @return Closure(callable((T|null), T, TKey, Iterator<TKey, T>): (T|null)):Closure (T|null): Closure(Iterator<TKey, T>): Generator<TKey, T>
* @template V
*
* @return Closure(callable(V, T, TKey, Iterator<TKey, T>): V): Closure (V): Closure(Iterator<TKey, T>): Generator<TKey, V>
*/
public function __invoke(): Closure
{
return
/**
* @param callable(T|null, T, TKey, Iterator<TKey, T>):(T|null) $callback
* @param callable(V, T, TKey, Iterator<TKey, T>): V $callback
*
* @return Closure(T|null): Closure(Iterator<TKey, T>): Generator<TKey, T>
* @return Closure(V): Closure(Iterator<TKey, T>): Generator<TKey, V>
*/
static fn (callable $callback): Closure =>
/**
* @param T|null $initial
* @param V $initial
*
* @return Closure(Iterator<TKey, T>): Generator<TKey, T>
* @return Closure(Iterator<TKey, T>): Generator<TKey, V>
*/
static fn ($initial = null): Closure =>
static fn ($initial): Closure =>
/**
* @param Iterator<TKey, T> $iterator
*
* @return Generator<TKey, T|null>
* @return Generator<TKey, V>
*/
static function (Iterator $iterator) use ($callback, $initial): Generator {
foreach ($iterator as $key => $value) {
Expand Down
3 changes: 1 addition & 2 deletions src/Operation/Reverse.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ public function __invoke(): Closure
/** @var Closure(Iterator<TKey, T>): Generator<TKey, T> $pipe */
$pipe = Pipe::of()(
Pack::of(),
Reduction::of()($callback)([]),
Last::of(),
Reduce::of()($callback)([]),
Unpack::of(),
);

Expand Down
2 changes: 1 addition & 1 deletion src/Operation/Unzip.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ static function (array $carry, iterable $value): array {

/** @var Closure(Iterator<TKey, list<T>>): Generator<int, list<T>> $pipe */
$pipe = Pipe::of()(
FoldLeft::of()($reduceCallback)([]),
Reduce::of()($reduceCallback)([]),
Flatten::of()(1)
);

Expand Down
Loading

0 comments on commit 18a68c4

Please sign in to comment.