6

高度な正規表現手法に関するpolygenelubricantsの一連の記事 (特にHow does this Java regex detect palindromes? )を読んだ後、再帰 (PHP で) を使用して、回文を解析する独自の PCRE 正規表現を作成することを試みることにしました。

私が思いついたのは:

^(([a-z])(?1)\2|[a-z]?)$

この表現についての私の理解では、0 文字または 1 文字 (2 文字未満のすべての文字列は暗黙的に回文であり、再帰で奇数の長さの回文を説明するため) に一致するか、2 つの同じ文字が分離されている必要があります。パターンの再帰によって。

残念ながら、 www.ideone.com/a9T3Fで確認できるように、そのようには機能していないようです。代わりに、2 n - 1 文字の文字列 (つまり、空の文字列、a, aaa, aaaaaaa, a 15 ) の繰り返し文字のみが正規表現に一致します。

奇妙なことに、再帰がオプションになるようにパターンを変更すると (つまり、 www.ideone.com/D6lJR^(([a-z])(?1)?\2|[a-z]?)$を参照してください。2 n回繰り返される文字 (つまり、空の文字列、、、、、a 16 )を持つ文字列にのみ一致します。 .aaaaaaaaaaaaaaa

正規表現が期待どおりに機能しないのはなぜですか?

正規表現を使用しないことを提案したくてうずうずしている人々への注意:
この質問のポイントは、再帰的な正規表現を適切に使用する方法を学ぶことです。これは、文字列が回文かどうかを判断する効果的な方法ではないことはわかっています。また、何らかの理由で製品コードで回文を判断する必要がある場合は、再帰的な正規表現を使用しません。正規表現の高度な側面についてもっと知りたいだけです。

4

2 に答える 2

8

あなたが観察している現象は、Perl とは異なり、PCRE サブパターンの再帰が原子的であるという事実によるものです。man ページには、実際にこの問題が非常に詳細に記載されています。

PCRE では (Python に似ていますが、Perl とは異なります)、再帰的なサブパターン呼び出しは 常にアトミック グループ として扱われます。つまり、サブジェクト文字列の一部と一致すると、未試行の代替が含まれていて、その後の一致の失敗があったとしても、再入力されることはありません

これは、奇数個の文字 (たとえば、、、、) を含む回文文字列に一致すると主張する次のパターンで説明 "a"でき"aba"ます。"abcba""abcdcba"

    ^(.|(.)(?1)\2)$

これは、単一の文字、またはサブパリンドロームを囲む 2 つの同一の文字のいずれかに一致するという考え方です。Perl では、このパターンが機能します。PCRE では、パターンが 3 文字より長い場合はそうではありません

件名の文字列を考えてみましょう"abcba":

最上位では、最初の文字が一致しますが、文字列の最後ではないため、最初の代替は失敗します。サブパターン 1 への再帰呼び出しは、次の文字 ( "b") と正常に一致します。(行頭テストと行末テストは再帰の一部ではないことに注意してください)。

最上位レベルに戻ると、次の文字 ( "c") が、サブパターン 2 が一致したもの ( ) と比較され"a"ます。これは失敗します。再帰はアトミック グループとして扱われるため、バックトラッキング ポイントがなくなり、一致全体が失敗します。(この時点で、Perl は再帰を再入力して 2 番目の選択肢を試すことができます。) しかし、パターンが別の順序で選択肢とともに書かれている場合、状況は異なります:

    ^((.)(?1)\2|.)$

今回は、再帰の代替案が最初に試行され、再帰が失敗する時点で文字がなくなるまで再帰を続けます。しかし今回は、より高いレベルで試す別の選択肢があります。それが大きな違いです。前のケースでは、残りの代替手段はより深い再帰レベルにあり、PCRE では使用できません。

文字数が奇数の文字列だけでなく、すべての回文文字列に一致するようにパターンを変更するには、パターンを次のように変更したくなるでしょう。

    ^((.)(?1)\2|.?)$

繰り返しますが、これは Perl では機能しますが、PCRE では機能しません。同じ理由で. より深い再帰が単一の文字に一致した場合、空の文字列に一致するために再度入力することはできません。解決策は、2 つのケースを分離し、奇数と偶数のケースを上位レベルの代替案として書き出すことです。

    ^(?:((.)(?1)\2|)|((.)(?3)\4|.))$

警告!!!

上記の回文マッチング パターンは、対象文字列が文字列全体よりも短い回文で始まっていない場合にのみ機能します。たとえば、"abcba"は正しく一致しますが、件名が の場合"ababa"、PCRE は"aba"最初に回文を見つけ、文字列の最後が続いていないため、最上位で失敗します。繰り返しになりますが、再帰に戻って他の選択肢を試すことはできないため、一致全体が失敗します。

その他の参考資料

  • regular-expressions.info/アトミック グループ化
    • (?>…)一部のフレーバーは、アトミック グループ化構文です。
    • ルックアラウンド(?=…)(?!…)(?<=…)(?<!…)はすべてアトミックです
    • 所有量指定子 (例: a*+) もアトミックです
    • PCRE 再帰サブパターンとサブルーチン呼び出しもアトミックです

パターンを詳しく見る

原子性の議論は正しいですが、パターンが観察されたように振る舞う理由をどのように説明するかはおそらく明らかではありません。詳しく見て、これがどのように適合するかを見てみましょう。

最初のパターンを使用します。

^(([a-z])(?1)\2|[a-z]?)$

再帰を表すために次の表記を使用します。

  • 1キャラクターが最初の代替でグループ 2 にキャプチャされたことを意味します
  • 2文字が 2 番目の代替文字と一致したことを意味します
    • または、2が文字の上にない場合、 のゼロ繰り返しオプション?が実行されます
  • \文字が最初の代替でグループ 2 への後方参照によって一致したことを意味します
  • _再帰的な枝の底を示します
    • 他の選択肢があっても、この分岐は再入力されません!

"aaa"次に、入力として考えてみましょう。

      _
1 1 1 2 
a a a   # This is the first bottom of the recursion,
        # now we go back to the third 1 and try to match \.
        # This fails, so the third 1 becomes 2.
    _
1 1 2
a a a   # Now we go back to the second 1 and try to match \.
        # This fails, so the second 1 becomes 2.
  _
1 2
a a a   # The second level matched! now we go back to the first level...

_____
1 2 \
a a a   # Now the first 1 can match \, and entire pattern matches!!

今考えてみましょう"aaaaa"

          _
1 1 1 1 1 2
a a a a a  # Fifth 1 can't match \, so it becomes 2. 
        _
1 1 1 1 2
a a a a a  # Fourth 1 can't match \, so it becomes 2.
    _____
1 1 1 2 /
a a a a a  # Here's a crucial point. The third 1 successfully matched.
           # Now we're back to the second 1 and try to match \, but this fails.
           # However, since PCRE recursion is atomic, the third 1 will NOT be
           # reentered to try 2. Instead, we try 2 on the second 1.
_____
1 2 \
a a a a a  # Anchors don't match, so the first 1 becomes 2, and then also the
           # anchors don't match, so the pattern fails to match.

PCRE サブパターンの再帰はアトミックであるため、最初の選択肢で再帰レベルが一致すると、2 番目の選択肢は将来 (たとえそうすることが一致する可能性があるとしても) 試行されないことに注意してください。


今考えてみましょう"aa"

    _
1 1 2 
a a
  _
1 2
a a  # The second level matched by taking the one repetition option on ?.
     # We now go back to the first level, and we can't match \.
     # Since PCRE recursion is atomic, we can't go back to the second level
     # to try the zero repetition option on ?.
_    
2
a a  # Anchors don't match, trying zero option on ? also doesn't help,
     # so the pattern fails to match!

PCRE サブパターンの再帰はアトミックであるため、再帰レベル?が 2 番目の選択肢の 1 回の繰り返しで一致すると、ゼロ繰り返しオプションは将来試行されないことに注意してください。


では、考えてみましょうaaaaaaa

              _
1 1 1 1 1 1 1 2  
a a a a a a a 
            _
1 1 1 1 1 1 2  
a a a a a a a 
        _____
1 1 1 1 1 2 \  
a a a a a a a  # A crucial point: the fifth level matched and now the fourth
               # level can't match \, but it does NOT reenter the fifth level to
               # try 2. Instead, the fourth level tries 2.
    _____    
1 1 1 2 \  
a a a a a a a 
  _________    
1 1 1 2 \ \ 
a a a a a a a 
_____________    
1 1 1 2 \ \ \
a a a a a a a  # Entire pattern is a match! 

PCRE サブパターンの再帰はアトミックですが、2 n -1 回繰り返される文字で構成される回文にうまく一致することに注意してください。


さて、楽しみのために、試してみましょう"abcba"

          _
1 1 1 1 1 2
a b c b a
        _
1 1 1 1 2
a b c b a

1 1 1 2 
a b c b a   # Third level attempts \, but c does not match a!
            # So we go back to third 1 and try 2.
  _____
1 1 2 \ 
a b c b a 
_________
1 1 2 \ \
a b c b a   # Entire pattern is a match!

つまり、パターンは「文字が 2 n -1 回繰り返される場合にのみ」一致するだけではありません。実際に一致する可能性があります"abcba"( ideone.com で見られるように)。ただし、 PCRE のサブパターン再帰はアトミックであるため、 と一致することも、一致"ababa"することもありません"aaaaa"(man ページの WARNING を参照してください)。

この同じトレース プロセスを適用して、任意の入力に対するパターンの動作を説明できます。

于 2010-09-18T04:37:13.007 に答える
1

回文に一致する完全に機能する PCRE 式が必要な場合は、次を使用できます。

/^(?:(.)(?=.*(\1(?(2)\2))$))*+.?\2?$/

于 2010-09-19T17:29:25.863 に答える