RegExでは、3つ以下の一意の文字を含む行をどのように見つけますか?
質問
大きなテキストファイルをループして、3文字以下の行を探しています(ただし、これらの文字は無期限に繰り返すことができます)。これを行う最善の方法は、ある種の正規表現だと思います。
すべてのヘルプに感謝します。
(PHPでスクリプトを書いていますが、それが役立ったら)
解決
おそらくこれは機能します:
preg_match("/^(.)\\1*(.)?(?:\\1*\\2*)*(.)?(?:\\1*\\2*\\3*)*$/", $string, $matches);
// aaaaa:Pass
// abababcaaabac:Pass
// aaadsdsdads:Pass
// aasasasassa:Pass
// aasdasdsadfasf:Fail
説明:
/
^ #start of string
(.) #match any character in group 1
\\1* #match whatever group 1 was 0 or more times
(.)? #match any character in group 2 (optional)
(?:\\1*\\2*)* #match group 1 or 2, 0 or more times, 0 or more times
#(non-capture group)
(.)? #match any character in group 3 (optional)
(?:\\1*\\2*\\3*)* #match group 1, 2 or 3, 0 or more times, 0 or more times
#(non-capture group)
$ #end of string
/
追加された特典、 $ matches [1]、[2]、[3]
には、必要な3文字が含まれます。正規表現は最初の文字を探し、それを保存し、その文字以外のものが見つかるまでそれを照合し、それを2番目の文字としてキャッチし、それらの文字のいずれかをできるだけ多くの回数一致させ、3番目の文字をキャッチし、一致が失敗するか、文字列が終了してテストに合格するまで、3つすべてに一致します。
編集
この正規表現は、解析エンジンとバックトラッキングの動作方法によりはるかに高速になります。説明については、bobinceの回答を参照してください。
/^(.)\\1*(?:(.)(?:\\1|\\2)*(?:(.)(?:\\1|\\2|\\3)*)?)?$/
他のヒント
子供向けの正規表現最適化楽しい時間の練習!開始点としてgnarfの正規表現を使用する:
^(.)\1*(.)?(?:\1*\2*)*(.)?(?:\1*\2*\3*)*$
ここにはネストされた*が連続していることに気付きました。これにより、多くのバックトラックが発生する可能性があります。たとえば、「abcaaax」では、‘ a’ sの最後の文字列を、長さ3の単一の\ 1 *、長さ2の\ 1 *の後に単一の\ 1、\ 1の後に2つの長さの\ 1 *、または3つの単一一致の\ 1が続きます。特に正規表現のために\ 1が\ 2と同じ文字であることを止めるものが何もない場合、この問題は文字列が長くなるとさらに悪化します。
^(.)\1*(.)?(?:\1|\2)*(.)?(?:\1|\2|\3)*$
これはオリジナルの2倍以上の速さで、PythonのPCREマッチャーでテストしました。 (PHPで設定するよりも高速です。申し訳ありません。)
これには、(。)?
が何にも一致せず、残りの一致を続行できるという問題があります。 \ 1 | \ 2
は、一致する\ 2がない場合でも\ 1に一致するため、 \ 1 | \ 2
と \ 1 | \ 2 | \ 3
句で、一致する結果が得られない場合。これは、末尾の句全体で?
オプションを移動することで解決できます。
^(.)\1*(?:(.)(?:\1|\2)*(?:(.)(?:\1|\2|\3)*)?)?$
これは再び2倍の速さでした。
\ 1、\ 2、および\ 3のいずれかが同じ文字になる可能性があるという潜在的な問題がまだあり、式が一致しない場合により多くのバックトラックを引き起こす可能性があります。これは、前の文字と一致しないように負の先読みを使用することで停止します:
^(.)\1*(?:(?!\1)(.)(?:\1|\2)*(?:(?!\1|\2)(.)(?:\1|\2|\3)*)?)?$
ただし、ランダムテストデータを使用したPythonでは、これによる大幅な高速化に気付きませんでした。 PHPでのテストデータに応じて走行距離は異なる場合がありますが、すでに十分である可能性があります。ここで入手できる場合、所有マッチング(* +)が役立ったかもしれません。
読みやすいPythonの選択肢よりも優れた正規表現はありません:
len(set(s))<=3
PHPの類似の方法は、おそらく count_chars :
strlen(count_chars($s, 3))<=3
速度はテストしていませんが、読むのにはるかに優れていることに加えて、これが正規表現よりも高速であると非常に期待しています。
したがって、基本的には正規表現をいじるだけで時間を無駄にしました。時間を無駄にしないでください。正規表現に頼る前に、まず単純な文字列メソッドを探してください!
降格のリスクがあるので、正規表現はこの状況を処理することを意図したものではないことをお勧めします。
1つの文字または文字のセットを一致させることはできますが、セットのどの文字がすでに見つかっているかを覚えておくと、それ以上の一致から除外されます。
文字セットを維持し、新しい行で始める前に文字セットをリセットし、行を移動しながら要素を追加することをお勧めします。セット内の要素の数が3を超えるとすぐに、現在の行を削除して次の行に進みます。
私にとって-正規表現の知識が十分にあるプログラマーとしては、Regexpのみを使用して解決できる問題とは思えません。
多くの場合、hashMap / arrayデータ構造キー:character value:countを構築し、大きなテキストファイルを反復処理して、各行のマップを再構築する必要があります。新しい文字が検出されるたびに、既に検出された文字数が2であるかどうかを確認します。2である場合は、現在の行をスキップします。
しかし、気違いの正規表現ハッカーが解決策を思い付くなら、驚くことを望んでいます。