あなたの問題は再帰です...あなたは何を知っていますか、再帰を忘れてください! =p PHP では実際にはうまく機能せず、それがなくてもアルゴリズムはかなり明確です。
function find_repeating_sequences($s)
{
$res = array();
while ($s) {
$i = 1; $pat = $s[0];
while (false !== strpos($s, $pat, $i)) {
$res[$pat] = 1;
// expand pattern and try again
$pat .= $s[$i++];
}
// move the string forward
$s = substr($s, 1);
}
return array_keys($res);
}
興味をそそられたので、 Tim の回答も PHP で書きました。
function find_repeating_sequences_re($s)
{
$res = array();
preg_match_all('/(?=(.+).*\1)/', $s, $matches);
foreach ($matches[1] as $match) {
$length = strlen($match);
if ($length > 1) {
for ($i = 0; $i < $length; ++$i) {
for ($j = $i; $j < $length; ++$j) {
$res[substr($match, $i, $j - $i + 1)] = 1;
}
}
} else {
$res[$match] = 1;
}
}
return array_keys($res);
}
800 バイトのランダム データの小さなベンチマークで、彼らに戦いを挑ませました。
$data = base64_encode(openssl_random_pseudo_bytes(600));
各コードを 10 ラウンド実行し、実行時間を測定します。結果?
Pure PHP - 0.014s (10 runs)
PCRE - 40.86s <-- ouch!
24kバイト(または実際には1kを超えるもの)を見ると、さらに奇妙になります。
Pure PHP - 4.565s (10 runs)
PCRE - 0.232s <-- WAT?!
正規表現が 1,000 文字で壊れ、$matches
配列が空になったことが判明しました。これらは私の.ini設定です:
pcre.backtrack_limit => 1000000 => 1000000
pcre.recursion_limit => 100000 => 100000
わずか 1,000 文字の後にバックトラックまたは再帰の制限がどのように達成されるかは、私には明らかではありません。しかし、それらの設定が何らかの形で「修正」されたとしても、結果は明らかであり、PCRE は答えではないようです。
これをCで書くといくらかスピードアップすると思いますが、どの程度かはわかりません。
アップデート
hakre's answer の助けを借りて、以下を最適化した後、パフォーマンスを最大 18% 向上させる改良版をまとめました。
外側のループ内の呼び出しを削除しsubstr()
て、文字列ポインターを進めます。これは、以前の再帰的な化身からの残り物でした。
部分的な結果をポジティブ キャッシュとして使用してstrpos()
、内部ループ内の呼び出しをスキップします。
そして、ここにそのすべての栄光があります (:
function find_repeating_sequences3($s)
{
$res = array();
$p = 0;
$len = strlen($s);
while ($p != $len) {
$pat = $s[$p]; $i = ++$p;
while ($i != $len) {
if (!isset($res[$pat])) {
if (false === strpos($s, $pat, $i)) {
break;
}
$res[$pat] = 1;
}
// expand pattern and try again
$pat .= $s[$i++];
}
}
return array_keys($res);
}