Skip to content

Commit

Permalink
Add Product operation to compute the cartesian product.
Browse files Browse the repository at this point in the history
  • Loading branch information
drupol committed Jan 2, 2020
1 parent f3dd8c0 commit 1940b16
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 0 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ Collection::with(['a', 'b', 'c', 'd', 'a'])
->flip()
->all(); // ['a', 'b', 'c', 'd', 'a']

// Get the Cartesian product.
Collection::with(['a', 'b'])
->product([1, 2])
->all(); // [['a', 1], ['a', 2], ['b', 1], ['b', 2]]

// Infinitely loop over numbers, cube them, filter those that are not divisible by 5, take the first 100 of them.
Collection::range(0, INF)
->map(
Expand Down Expand Up @@ -447,6 +452,7 @@ the methods always return the same values for the same inputs.
| `pad` | new Collection object | [Pad.php](./src/Operation/Pad.php)
| `pluck` | new Collection object | [Pluck.php](./src/Operation/Pluck.php)
| `prepend` | new Collection object | [Prepend.php](./src/Operation/Prepend.php)
| `product` | new Collection object | [Product.php](./src/Operation/Product.php)
| `rebase` | new Collection object | [Collection.php](./src/Operation/Collection.php)
| `reduce` | mixed | [Reduce.php](./src/Transformation/Reduce.php)
| `reduction` | new Collection object | [Reduction.php](./src/Operation/Reduction.php)
Expand Down
3 changes: 3 additions & 0 deletions docs/pages/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ pluck
prepend
-------

product
-------

rebase
------

Expand Down
14 changes: 14 additions & 0 deletions spec/loophp/collection/CollectionSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,20 @@ public function it_can_distinct(): void
->shouldIterateAs([0 => 1, 2 => 2, 4 => 3, 6 => $stdclass]);
}

public function it_can_do_the_cartesian_product(): void
{
$this
->beConstructedThrough('with', [range('A', 'C')]);

$this
->product()
->shouldIterateAs([0 => ['A'], 1 => ['B'], 2 => ['C']]);

$this
->product([1, 2])
->shouldIterateAs([0 => ['A', 1], 1 => ['A', 2], 2 => ['B', 1], 3 => ['B', 2], 4 => ['C', 1], 5 => ['C', 2]]);
}

public function it_can_explode(): void
{
$string = 'I am just a random piece of text.';
Expand Down
11 changes: 11 additions & 0 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use loophp\collection\Operation\Pad;
use loophp\collection\Operation\Pluck;
use loophp\collection\Operation\Prepend;
use loophp\collection\Operation\Product;
use loophp\collection\Operation\Range;
use loophp\collection\Operation\Reduction;
use loophp\collection\Operation\Reverse;
Expand Down Expand Up @@ -374,6 +375,16 @@ public function prepend(...$items): BaseInterface
return $this->run(new Prepend($items));
}

/**
* {@inheritdoc}
*
* @return \loophp\collection\Contract\Collection
*/
public function product(iterable ...$iterables): BaseInterface
{
return $this->run(new Product(...$iterables));
}

/**
* {@inheritdoc}
*
Expand Down
1 change: 1 addition & 0 deletions src/Contract/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ interface Collection extends
Padable,
Pluckable,
Prependable,
Productable,
Rebaseable,
Reduceable,
Reductionable,
Expand Down
20 changes: 20 additions & 0 deletions src/Contract/Productable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace loophp\collection\Contract;

/**
* Interface Productable.
*/
interface Productable
{
/**
* Compute the cartesian product.
*
* @param iterable ...$iterables
*
* @return \loophp\collection\Contract\Collection<mixed>
*/
public function product(iterable ...$iterables): Base;
}
77 changes: 77 additions & 0 deletions src/Operation/Product.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace loophp\collection\Operation;

use Closure;
use Generator;
use loophp\collection\Contract\Operation;
use loophp\collection\Iterator\IterableIterator;

use function count;

/**
* Class Product.
*/
final class Product implements Operation
{
/**
* @var iterable[]
*/
private $iterables;

/**
* Product constructor.
*
* @param iterable ...$iterables
*/
public function __construct(iterable ...$iterables)
{
$this->iterables = $iterables;
}

/**
* {@inheritdoc}
*/
public function on(iterable $collection): Closure
{
$iterables = $this->iterables;

$cartesian = function (array $input): Generator {
return $this->cartesian($input);
};

return static function () use ($iterables, $collection, $cartesian): Generator {
$its = [$collection];

foreach ($iterables as $iterable) {
$its[] = new IterableIterator($iterable);
}

yield from $cartesian($its);
};
}

/**
* @param array<iterable> $iterators
*
* @return Generator<array>
*/
private function cartesian(array $iterators): Generator
{
$last = array_pop($iterators);

if (null === $last) {
yield [];

return;
}

foreach ($this->cartesian($iterators) as $item) {
foreach ($last as $value) {
yield $item + [count($item) => $value];
}
}
}
}

0 comments on commit 1940b16

Please sign in to comment.