5

私たちのパターンが大文字の正規表現であるとしましょう (ただし、大文字を検索するよりも複雑なパターンを持つことができます)

少なくともn 個の連続したパターン (この場合、探しているパターンは大文字のみ)を見つけるには、次のようにします。

(ルビー使用)

somestring = "ABC deFgHij kLmN pQrS XYZ abcdEf"

at_least_2_capitals = somestring.scan(/[A-Z][A-Z]+/)
=> ["ABC", "XYZ"]
at_least_3_capitals = somestring.scan(/[A-Z]{3}[A-Z]*/)
=> ["ABC", "XYZ"]

ただし、最大n 個の連続するパターン (たとえば、最大 1 個の連続する大文字)を検索するにはどうすればよいですか。

matches = somestring.scan(/ ??? /)
=> [" deFgHij kLmN pQrS ", " abcdEf"]

詳細な戦略

「少なくとも」正規表現をDFAに変換し、受け入れ状態を否定して否定する必要があることを読みました(その後、NFAに戻しますが、そのままにしておくことができます)ので、正規表現として記述します. 「1」を受け取ったパターンに遭遇し、「0」を受け取ったパターンを受け取っていないと考える場合、単純な DFA ダイアグラムを描くことができます (n=1 の場合、パターンは最大で 1 つ必要です)。

DFA_to_be_regexed

具体的には、これがどのように正規表現になるのか疑問に思っていました。一般的に、私の正規表現スキルは「少なくとも」だけでは発育不全になっていると感じているため、正規表現で「最大」を見つける方法を見つけたいと思っています。


つまずきの危険 - 精神的には適切な解決策とは言えません

この質問は、この投稿の複製ではないことに注意してください。受け入れられている方法論を使用すると、次のようになります。

somestring.scan(/[A-Z]{2}[A-Z]*(.*)[A-Z]{2}[A-Z]*/)
=> [[" deFgHij kLmN pQrS X"]]

これは、DFA が示すものではありません。2 番目に求められた一致を逃したという理由だけではありません。さらに重要なのは、「X」の後に別の大文字が続くため、「X」が含まれているべきではないということです。DFA からは、別の大文字が続く大文字は、受け入れ状態ではありません。

あなたは提案することができます

somestring.split(/[A-Z]{2}[A-Z]*/)
=> ["", " deFgHij kLmN pQrS ", " abcdEf"]

(ラバーダックさんに感謝)

しかし、正規表現のみを使用して最大 n 回の出現を見つける方法を知りたいと思っています。(知識のために!)

4

3 に答える 3

2

あなたの試みがうまくいかない理由

現在の試みにはいくつかの問題があります。

  1. X一致の一部である理由は、.*貪欲であり、可能な限り多くを消費するためです。したがって、必要な 2 つの大文字のみを末尾のビットと一致させる必要があります。これは、貪欲でない量指定子で修正できます。
  2. 2 番目の一致が得られない理由は 2 つあります。まず、2 つの末尾の大文字が必要ですが、代わりに文字列の末尾があります。第二に、一致は重複できません。最初の一致には少なくとも 2 つの末尾の大文字が含まれていますが、2 番目の一致では最初にこれらを再度一致させる必要がありますが、これは不可能です。
  3. さらに隠れた問題があります: 大文字を 4 つ連続して入力してみてください - 空の一致が得られる可能性があります (貪欲でない量指定子を使用している場合 - 貪欲な量指定子にはさらに悪い問題があります)。

現在のアプローチでこれらすべてを修正するのは困難です (私は試してみましたが失敗しました。このアプローチを完全に破棄することを決定するまで、私の試みを見たい場合は、この投稿の編集履歴を確認してください)。それでは、他のことを試してみましょう。

別の解決策を探す

一致させたいのは何ですか?一致が文字列の先頭で開始するか、文字列の最後で終了するエッジ ケースを無視して、次のように一致させます。

(非キャップ) 1 キャップ (非キャップ) 1 キャップ (非キャップ) ... .

これは、Jeffrey Friedl のunrolling-the-loop に最適です。どのように見える

[^A-Z]+(?:[A-Z][^A-Z]+)*

では、エッジ ケースについてはどうでしょうか。次のように表現できます。

  1. 文字列の先頭にある場合にのみ、一致の先頭に単一の大文字を許可したいと考えています。
  2. 文字列の末尾にある場合にのみ、一致の最後に単一の大文字を許可したいと考えています。

これらをパターンに追加するには、大文字を適切なアンカーでグループ化し、両方をオプションとして一緒にマークします。

(?:^[A-Z])?[^A-Z]+(?:[A-Z][^A-Z]+)*(?:[A-Z]$)?

今、それは本当に働いています。さらに良いことに、これ以上キャプチャする必要はありません!

ソリューションの一般化

このソリューションは、「最大でn 個の連続する大文字」の場合に簡単に一般化できます。それぞれ[A-Z]を に変更[A-Z]{1,n}することで、nこれまで 1 つしか許可されていなかった大文字まで許可します。

のデモを参照してくださいn = 2

于 2013-06-19T10:27:23.753 に答える
2

tl;dr

最大で s を含む単語に一致さN PATTERNせるには、正規表現を使用します

/\b(?:\w(?:(?<!PATTERN)|(?!(?:PATTERN){N,})))+\b/

たとえば、最大 1 つの大文字を含む単語を一致させるには、次のようにします。

/\b(?:\w(?:(?<![A-Z])|(?!(?:[A-Z]){1,})))+\b/

これは、複数文字のパターンでも機能します。


明確化が必要

あなたの例が混乱を招くのではないかと心配しています。いくつかの言葉を追加しましょう:

somestring = "ABC deFgHij kLmN pQrS XYZ abcdEf mixedCaps mixeDCaps mIxedCaps mIxeDCaps T TT t tt"
                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ここで、少なくとも 2 つの大文字の正規表現を再実行すると、戻り値が返されます

at_least_2_capitals = somestring.scan(/[A-Z][A-Z]+/)
=> ["ABC", "XYZ", "DC", "DC", "TT"]

完全な単語がキャプチャされないことに注意してください。これがあなたが望んでいたものであると確信していますか?もちろん、後者の例では、大文字がキャプチャされるだけでなく、最大1つの大文字の正規表現が完全な単語を返すためです。


解決

ここでは、いずれかの方法で解決策を示します。

まず、パターンのみを一致させるために(最初の例と一致するように、単語全体ではなく)、at-most- N- PATTERNs の正規表現を次に示します。

/(?<!PATTERN)(?!(?:PATTERN){N+1,})(?:PATTERN)+/

たとえば、最大 1 大文字の正規表現は次のようになります。

/(?<![A-Z])(?!(?:[A-Z]){2,})(?:[A-Z])+/

そして戻る

=> ["F", "H", "L", "N", "Q", "S", "E", "C", "DC", "I", "C", "I", "DC", "T", "TT"]

さらに例を挙げると、最大 2 大文字の正規表現は次を返します。

=> 

最後に、最大で特定の数の連続したパターンを含む単語全体を一致させたい場合は、まったく異なるアプローチを次に示します。

/\b(?:\w(?:(?<![A-Z])|(?![A-Z]{1,})))+\b/

これは戻ります

["deFgHij", "kLmN", "pQrS", "abcdEf", "mixedCaps", "mIxedCaps", "T", "t", "tt"]

一般的な形式は

/\b(?:\w(?:(?<!PATTERN)|(?!(?:PATTERN){N,})))+\b/

これらの例はすべてhttp://ideone.com/hImmZrで見ることができます。

于 2013-06-19T10:29:42.113 に答える
1

正規表現で「最大」を見つけるには、接尾辞を使用します{1,n}(おそらく、否定的な後読みが前にあり、その後に肯定的な先読みが続きます)。したがって、必要なものは次のようになります。

irb(main):006:0> somestring.scan(/[A-Z]{1,2}/)
=> ["AB", "C", "F", "H", "L", "N", "Q", "S", "XY", "Z", "E"]

また

irb(main):007:0> somestring.scan(/(?<![A-Z])[A-Z]{1,2}(?![A-Z])/)
=> ["F", "H", "L", "N", "Q", "S", "E"]

EDIT:OPがまだ「2つ以上の大文字を含まない最長の文字列」を必要とする場合は、次を使用できます:

irb(main):025:0> somestring.scan(/[^A-Z]+(?:[A-Z]{1,2}[^A-Z]+)*/)                                                                                    
=> [" deFgHij kLmN pQrS ", " abcdEf"]

(ただし、その正規表現は文字列の最初と最後で一致しない可能性があります)

のようだ

irb(main):026:0> somestring.split(/[A-Z]{3,}/)                                                                                                       
=> ["", " deFgHij kLmN pQrS ", " abcdEf"]

その方がいいでしょう。

于 2013-06-19T10:16:48.163 に答える