I want simply to find-out better way to do this:
$array = array(
array(\'a\', \'b\', \'c\'),
array(\'e\', \'f\', \'g\'),
array(\'h\', \'i\', \'j\'
I first opted for some iteration (counting) based solution directly working on the arrays. As it turned out, this was just a loop with multiple counters. Then I thought, why limit this onto arrays? Why not using a CartesianProductIterator that creates an iteration over the cartesian product of multiple iterators?
It works similar to an AppendIterator and MultipleIterator (see as well: Iterating over Multiple Iterators at Once (Apr 2012; by hakre)) and can be easily used with your array:
$array = [
['a', 'b', 'c'],
['e', 'f', 'g'],
['h', 'i', 'j', 'k', 'l'],
];
$it = new CartesianProductIterator();
foreach($array as $sub)
{
$it->append(new ArrayIterator($sub));
}
foreach ($it as $tuple)
{
echo implode(',', $tuple), "\n";
}
The output is as expected:
a,e,h
a,e,i
a,e,j
...
c,g,j
c,g,k
c,g,l
A benefit of such an iterator is that it is more flexible for what it accepts as input.
Also those products can be extremely memory demaning, an iterator is a good tool to reduce memory requirements by solving the problem iteratively.
Another benefit is that an Iterator
already keeps track of counting, so the counting problem per each dimension of the product is already solved.
As long as the memory consumption is concerned, instead of an iterator that implements the counting for you, you can do the iteration over all tuples in the product as well with a loop. This variant btw. should actually take care of keys, so should work with different inputs (the example code is reduced for reading):
...
// working loop
$valid = TRUE;
while ($valid)
{
// create segment
foreach ($array as $key => $sub)
{
echo $sub[$keys[$key][$counters[$key]]];
}
echo "\n";
// count up
foreach ($order as $key)
{
if (++$counters[$key] == $lengths[$key])
{
$counters[$key] = 0;
continue;
}
continue 2;
}
$valid = FALSE;
};
As this example shows, each iteration in the loop outputs one tuple of the product, so memory requirements are low, too. If you replace the echo
with a yield
statement this is a good boilerplate to create a generator from.
As the CartesianProdcutIterator
is an object so it can do a little more than a loop or generator, therefore it has some more features: You can specify the iteration or counting or sort mode: Move on the last iterator first (default), or the first first:
$it = new CartesianProductIterator(CartesianProductIterator::ORDER_FIRST_FIRST);
This will do the following iteration:
a,e,h
b,e,h
c,e,h
...
a,g,l
b,g,l
c,g,l
But not only that, it can be even controlled more by specifying a $countOrder
parameter when appending. It specifies the actual sort-keys to be ordered on by the order-mode:
$array = [
0 => ['a', 'b', 'c'],
2 => ['e', 'f', 'g'],
1 => ['h', 'i', 'j', 'k', 'l'],
];
$it = new CartesianProductIterator();
foreach($array as $countOrder => $sub)
{
$it->append(new ArrayIterator($sub), $countOrder);
}
foreach ($it as $tuple)
{
echo implode(',', $tuple), "\n";
}
This (as the default mode is last-first) specifies to first iterate in the middle (e-g), then at the end (h-l) and then on the first one (a-c):
a,e,h
a,f,h
a,g,h
a,e,i
...
c,g,k
c,e,l
c,f,l
c,g,l
Hope this is helpful and qualifies as "a better way".