diff --git a/README.md b/README.md index 4f59cb276..125f09d23 100644 --- a/README.md +++ b/README.md @@ -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( @@ -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) diff --git a/docs/pages/api.rst b/docs/pages/api.rst index a774ae271..36b326b28 100644 --- a/docs/pages/api.rst +++ b/docs/pages/api.rst @@ -82,6 +82,9 @@ pluck prepend ------- +product +------- + rebase ------ diff --git a/spec/loophp/collection/CollectionSpec.php b/spec/loophp/collection/CollectionSpec.php index 2c59a6418..4b6cf7b98 100644 --- a/spec/loophp/collection/CollectionSpec.php +++ b/spec/loophp/collection/CollectionSpec.php @@ -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.'; diff --git a/src/Collection.php b/src/Collection.php index 58e94cb37..e2f8e2c04 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -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; @@ -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} * diff --git a/src/Contract/Collection.php b/src/Contract/Collection.php index ea5fa558c..093fa1a9e 100644 --- a/src/Contract/Collection.php +++ b/src/Contract/Collection.php @@ -40,6 +40,7 @@ interface Collection extends Padable, Pluckable, Prependable, + Productable, Rebaseable, Reduceable, Reductionable, diff --git a/src/Contract/Productable.php b/src/Contract/Productable.php new file mode 100644 index 000000000..98efe3e3b --- /dev/null +++ b/src/Contract/Productable.php @@ -0,0 +1,20 @@ + + */ + public function product(iterable ...$iterables): Base; +} diff --git a/src/Operation/Product.php b/src/Operation/Product.php new file mode 100644 index 000000000..bda26c433 --- /dev/null +++ b/src/Operation/Product.php @@ -0,0 +1,77 @@ +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 $iterators + * + * @return Generator + */ + 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]; + } + } + } +}