ステージの設定
"0..9"
まず、次のように文字列を展開"0123456789"
する関数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;
}
任意の数の展開可能なパターンを処理し、ソース文字列内の位置を維持するように注意します。たとえば、
expand_pattern('abc0..4def5..9')
戻り"abc01234def56789"
ます。
一度に結果を計算する
この拡張を簡単に実行できるようになったので、許可された文字列と長さを指定してデカルト積を計算する関数を次に示します。
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;
}
たとえば、質問で説明されている出力を取得するには、次のようにします。
$options = cartesian(expand_pattern('a..z0..9'), 3);
実際の動作を見てください(出力が爆発しないように、展開の長さを 2 に制限しました)。
その場で結果を生成する
結果セットは非常に大きくなる可能性があるため (指数関数的に大きくなる$length
)、すべてを一度に生成すると非常に大きなコストがかかる場合があります。その場合、各値を順番に返すようにコードを書き直すことができます (イテレータ スタイル)。これは、ジェネレーターのおかげで PHP 5.5 で非常に簡単になりました。
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;
}
}
}
実際に見てください。