Setting the stage
First, a function that expands strings like "0..9"
to "0123456789"
using range
:
function expand_pattern($pattern) {
$bias = 0;
$flags = PREG_SET_ORDER | PREG_OFFSET_CAPTURE;
preg_match_all('/(.)\.\.(.)/', $pattern, $matches, $flags);
foreach ($matches as $match) {
$range = implode('', range($match[1][0], $match[2][0]));
$pattern = substr_replace(
$pattern,
$range,
$bias + $match[1][1],
$match[2][1] - $match[1][1] + 1);
$bias += strlen($range) - 4; // 4 == length of "X..Y"
}
return $pattern;
}
It handles any number of expandable patterns and takes care to preserve their position inside your source string, so for example
expand_pattern('abc0..4def5..9')
will return "abc01234def56789"
.
Calculating the result all at once
Now that we can do this expansion easily, here's a function that calculates cartesian products given a string of allowed characters and a length:
function cartesian($pattern, $length) {
$choices = strlen($pattern);
$indexes = array_fill(0, $length, 0);
$results = array();
$resets = 0;
while ($resets != $length) {
$result = '';
for ($i = 0; $i < $length; ++$i) {
$result .= $pattern[$indexes[$i]];
}
$results[] = $result;
$resets = 0;
for ($i = $length - 1; $i >= 0 && ++$indexes[$i] == $choices; --$i) {
$indexes[$i] = 0;
++$resets;
}
}
return $results;
}
So for example, to get the output described in the question you would do
$options = cartesian(expand_pattern('a..z0..9'), 3);
See it in action (I limited the expansion length to 2 so that the output doesn't explode).
Generating the result on the fly
Since the result set can be extremely large (it grows exponentially with $length
), producing it all at once can turn out to be prohibitive. In that case it is possible to rewrite the code so that it returns each value in turn (iterator-style), which has become super easy with PHP 5.5 because of generators:
function cartesian($pattern, $length) {
$choices = strlen($pattern);
$indexes = array_fill(0, $length, 0);
$resets = 0;
while ($resets != $length) {
$result = '';
for ($i = 0; $i < $length; ++$i) {
$result .= $pattern[$indexes[$i]];
}
yield $result;
$resets = 0;
for ($i = $length - 1; $i >= 0 && ++$indexes[$i] == $choices; --$i) {
$indexes[$i] = 0;
++$resets;
}
}
}
See it in action.