Skip to content

Instantly share code, notes, and snippets.

@martinezdelariva
Created March 30, 2015 11:24
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save martinezdelariva/621553a2b8bef8cd6f2b to your computer and use it in GitHub Desktop.
Save martinezdelariva/621553a2b8bef8cd6f2b to your computer and use it in GitHub Desktop.
Cartesian Product Iterator
// Usage:
$cartesianProductIt = new CartesianProduct(array(
new \ArrayIterator(array(1, 2)),
new \ArrayIterator(array('a', 'b'))
));
// Output on traverse
// array(1, 'a'),
// array(1, 'b'),
// array(2, 'a'),
// array(2, 'b'),
/**
* Class CartesianProductIterator
*
* When traversing return arrays of items of the Cartesian Product for the set of iterators given.
*
* Input : array of Iterators.
* Output : array of items of Cartesian Product.
*/
class CartesianProductIterator implements \Iterator
{
/**
* @var \int
*/
protected $key;
/**
* @var \Iterator[]
*/
protected $iterators = array();
/**
* @param \Iterator[] $iterators
*/
public function __construct(array $iterators)
{
$this->iterators = $iterators;
$this->key = 0;
}
/**
* @inheritdoc
*/
public function current()
{
$cartesianItem = array();
foreach ($this->iterators as $iterator) {
$cartesianItem[] = $iterator->current();
}
return $cartesianItem;
}
/**
* @inheritdoc
*/
public function next()
{
/** @var $reverseIterators \Iterator[] */
$reverseIterators = array_reverse($this->iterators);
foreach ($reverseIterators as $key => $iterator) {
$iterator->next();
if ($iterator->valid()) {
$this->rewindIteratorsFromPosition(count($this->iterators) - $key);
break;
}
}
$this->key++;
}
/**
* Rewind the latest iterators from $position given
*
* @param int $position
*/
protected function rewindIteratorsFromPosition($position)
{
foreach ($this->iterators as $key => $iterator) {
if ($key >= $position) {
$iterator->rewind();
}
}
}
/**
* Rewind the firsts iterators until $position given
*
* @param int $position
*/
protected function rewindIteratorsUntilPosition($position)
{
foreach ($this->iterators as $key => $iterator) {
if ($key <= $position) {
$iterator->rewind();
}
}
}
/**
* @inheritdoc
*/
public function key()
{
return $this->key;
}
/**
* @inheritdoc
*/
public function valid()
{
$isUnlessOneValid = false;
foreach ($this->iterators as $iterator) {
if ($iterator->valid()) {
$isUnlessOneValid = true;
}
}
return $isUnlessOneValid;
}
/**
* @inheritdoc
*/
public function rewind()
{
foreach ($this->iterators as $iterator) {
$iterator->rewind();
}
$this->key = 0;
}
}
@martinezdelariva
Copy link
Author

TODO:

  • Pending return empty set when any Iterator given is empty

@goreilly
Copy link

thank you, no more nested nested nested foreach loops

@slowkow
Copy link

slowkow commented Oct 27, 2016

You may be interested Hoa\Math\Combinatorics\Combination\CartesianProduct as mentioned in this comment.

Here is the test for the case when the input is empty.

$product = new Hoa\Math\Combinatorics\Combination\CartesianProduct(
    ['Red', 'White', 'Blue'],
    [1, 2, 3, 4],
    ['Cloth', 'Silk']
);

foreach($product as $i => $tuple)
    echo $i, ' = (', implode(', ', $tuple), ')', "\n";
0 = (Red, 1, Cloth)
1 = (White, 1, Cloth)
2 = (Blue, 1, Cloth)
3 = (Red, 2, Cloth)
4 = (White, 2, Cloth)
5 = (Blue, 2, Cloth)
6 = (Red, 3, Cloth)
7 = (White, 3, Cloth)
8 = (Blue, 3, Cloth)
9 = (Red, 4, Cloth)
10 = (White, 4, Cloth)
11 = (Blue, 4, Cloth)
12 = (Red, 1, Silk)
13 = (White, 1, Silk)
14 = (Blue, 1, Silk)
15 = (Red, 2, Silk)
16 = (White, 2, Silk)
17 = (Blue, 2, Silk)
18 = (Red, 3, Silk)
19 = (White, 3, Silk)
20 = (Blue, 3, Silk)
21 = (Red, 4, Silk)
22 = (White, 4, Silk)
23 = (Blue, 4, Silk)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment