33

文字列から多数のトークンをキャプチャする必要がある状況に何度も遭遇し、数え切れないほど試行した後、プロセスを簡素化する方法を見つけることができませんでした。

では、テキストが次のようになっているとしましょう。

開始: テスト-テスト-lorem-ipsum-sir-doloret-etc-何か: 終了

この例では、内部に 8 つのアイテムがありますが、3 ~ 10 個のアイテムを含めることができるとします。

理想的には、次のようなものが欲しいです。
start:(?:(\w+)-?){3,10}:endすてきできれいですが、最後の試合しかキャプチャしません。ここを参照

私は通常、単純な状況で次のようなものを使用します。

start:(\w+)-(\w+)-(\w+)-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?-?(\w+)?:end

最大 10 個の制限があるため、3 つのグループは必須で、別の 7 つのグループはオプションですが、これは見栄えがよくなく、最大制限が 100 で、一致がより複雑な場合、記述して追跡するのは面倒です。デモ

そして、これまでに私ができる最善のこと:

start:(\w+)-((?1))-((?1))-?((?1))?-?((?1))?-?((?1))?-?((?1))?-?((?1))?:end

特に一致が複雑であるがそれでも長い場合は短くなります。デモ

プログラミングせずに、1つの正規表現のみのソリューションとして機能させることができた人はいますか?

これを PCRE でどのように行うことができるかに主に関心がありますが、他のフレーバーも問題ありません。

アップデート:

match 0目的は、OS/ソフトウェア/プログラミング言語の制限なしに、一致を検証し、RegEx だけで内部の個々のトークンをキャプチャすることです。

更新 2 (報奨金):

@nhahtdh の助けを借りて、次を使用して以下の RegExp に到達しました\G

(?:start:(?=(?:[\w]+(?:-|(?=:end))){3,10}:end)|(?!^)\G-)([\w]+)

demoはさらに短くなりますが、コードを繰り返さずに記述できます

ECMA フレーバーにも興味があり、特に修飾子\Gを使用しない別の方法があるかどうか疑問に思うことをサポートしていないためです。/g

4

5 に答える 5

36

最初にこれを読んでください!

この投稿は、問題に対する「すべての正規表現」アプローチを支持するのではなく、可能性を示すことを目的としています。著者は 3 ~ 4 のバリエーションを作成しましたが、現在のソリューションにたどり着く前に、それぞれに微妙なバグがあり、検出が困難です。

特定の例では、区切り文字に沿って一致を一致させたり分割したりするなど、より保守しやすい他のより良いソリューションがあります。

この投稿では、特定の例を扱います。完全に一般化できるとは思えませんが、背後にあるアイデアは同様のケースで再利用できます。

概要

  • .NET は、CaptureCollectionクラスを使用した繰り返しパターンのキャプチャをサポートしています。
  • 後読みをサポートする言語\Gの場合、グローバル マッチング関数で動作する正規表現を構築できる場合があります。完全に正しく書くのは簡単ではなく、微妙にバグのある正規表現を書くのは簡単です。
  • \G後方参照をサポートしていない言語の場合:単一の一致後に入力文字列をチョッピングすることにより、 で\Gエミュレートできます。^(この回答ではカバーされていません)。

解決

このソリューションは、正規表現エンジンが\G一致境界、先読み(?=pattern)、および後読みをサポートしていることを前提としてい(?<=pattern)ます。Java、Perl、PCRE、.NET、Ruby 正規表現は、上記の高度な機能をすべてサポートしています。

ただし、.NET で正規表現を使用できます。CaptureCollection.NET は、クラスを介して繰り返されるキャプチャ グループによって一致する のすべてのインスタンスのキャプチャをサポートしているためです。

\Gあなたの場合、一致境界と先読みを使用して繰り返し回数を制限することで、1つの正規表現で実行できます。

(?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end)

デモ。構築が\w+-繰り返され、その後\w+:end.

(?:start:(?=\w+(?:-\w+){2,9}:end)|(?!^)\G-)(\w+)

デモ。構築は\w+最初の項目のためのものであり、その後-\w+繰り返されます。(提案してくれたka ᵠに感謝します)。この構造は、変更が少ないため、その正確性について簡単に推論できます。

\G境界の一致は、トークン化を行う必要がある場合に特に役立ちます。この場合、エンジンが先にスキップせず、無効であるはずのものを照合する必要があります。

説明

正規表現を分解してみましょう:

(?:
  start:(?=\w+(?:-\w+){2,9}:end)
    |
  (?<=-)\G
)
(\w+)
(?:-|:end)

認識するのが最も簡単な部分は(\w+)、最後の行の前の行です。これは、キャプチャしたい単語です。

最後の行も非常に簡単に認識できます。一致する単語の後に-orが続く場合があります:end

正規表現がstring 内のどこからでも自由にマッチングを開始できるようにします。つまり、start:...:end文字列内のどこにでも何回でも出現できます。正規表現は単にすべての単語に一致します。返された配列を処理して、一致したトークンが実際にどこから来たのかを分離するだけで済みます。

説明としては、正規表現の先頭で文字列 の存在を確認しstart:、次の先読みで単語数が指定された制限内にあり、 で終わることを確認し:endます。それか、前の一致の前の文字が であることを確認して、前の一致-から続行します。

他の構造の場合:

(?:
  start:(?=\w+(?:-\w+){2,9}:end)
    |
  (?!^)\G-
)
(\w+)

start:\w+フォームの繰り返しを照合する前に最初に照合することを除いて、すべてがほぼ同じ-\w+です。最初に一致する最初の構成とは対照的に、(または最後の繰り返し) のstart:\w+-インスタンスが繰り返されます。\w+-\w+:end

この正規表現を文字列の途中で一致させるのは非常に難しいです:

  • (元の正規表現の要件の一部として)start:との間の単語数を確認する必要があります。:end

  • \G文字列の先頭にも一致します! (?!^)この動作を防ぐために必要です。これに注意しないと、正規表現が存在しない場合でも一致が生成される可能性がありますstart:

    最初の構成では、後読みは(?<=-)既にこのケースを防止しています ((?!^)によって暗示されてい(?<=-)ます)。

  • 最初の構築(?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end)では、 の後に面白いものと一致しないようにする必要があり:endます。:end後読みはその目的のためのものです。一致後のガベージを防ぎます。

    間にあるすべてのトークンを一致させた後で:(of )でスタックするため、2 番目の構成ではこの問題は発生しません。:end

検証バージョン

入力文字列がフォーマットに従っていること (前後に余分なものがないこと) を検証し、データ抽出する場合は、次のようにアンカーを追加できます。

(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G-)(\w+)
(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G)(\w+)(?:-|:end)

(後読みも必要ありませんが、文字列の先頭が一致(?!^)しないようにする必要があります)。\G

工事

繰り返しのすべてのインスタンスをキャプチャしたいすべての問題について、正規表現を変更する一般的な方法は存在しないと思います。変換が「難しい」(または不可能?) ケースの 1 つの例は、一致する特定の条件を満たすために繰り返しが 1 つまたは複数のループをバックトラックする必要がある場合です。

元の正規表現が入力文字列全体を記述している場合 (検証型)、文字列の途中から照合しようとする正規表現 (照合型) に比べて、通常は変換が容易です。ただし、いつでも元の正規表現との照合を行うことができ、照合タイプの問題を検証タイプの問題に変換します。

次の手順を実行して、そのような正規表現を構築します。

  • 繰り返しの前の部分をカバーする正規表現を記述します (例: start:)。このプレフィックスを regexと呼びましょう。
  • 最初のインスタンスを一致させてキャプチャします。(例(\w+))
    (この時点で、最初のインスタンスと区切り文字が一致している必要があります)
  • \Gを代替として追加します。通常、文字列の先頭と一致しないようにする必要もあります。
  • 区切り文字を追加します (存在する場合)。(例-)
    (このステップの後、最後の多分を除いて、残りのトークンも一致しているはずです)
  • 繰り返しの後の部分を覆う部分を追加します (必要な場合) (例:end)。繰り返しサフィックスの後の部分を regexと呼びましょう(構造に追加するかどうかは問題ではありません)。
  • 今、難しい部分です。次のことを確認する必要があります。
    • プレフィックス regex以外に、一致を開始する方法はありません。\G枝に注意してください。
    • サフィックスの正規表現が一致した後に一致を開始する方法はありません。ブランチがマッチを開始する方法に注意してください。\G
    • 最初の構成では、接尾辞の正規表現 (例: :end) と区切り文字 (例: -) を交互に混在させる場合は、接尾辞の正規表現を区切り文字として許可しないようにしてください。
于 2013-03-14T19:54:40.617 に答える
6

理論的には単一の式を記述することは可能かもしれませんが、最初に外側の境界を一致させてから、内側の部分で分割を実行する方がはるかに実用的です。

ECMAScript では、次のように記述します。

'start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end'
    .match(/^start:([\w-]+):end$/)[1] // match the inner part
    .split('-') // split inner part (this could be a split regex as well)

PHP の場合:

$txt = 'start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end';
if (preg_match('/^start:([\w-]+):end$/', $txt, $matches)) {
    print_r(explode('-', $matches[1]));
}
于 2013-04-10T09:39:50.097 に答える
1

もちろん、この引用符で囲まれた文字列で正規表現を使用できます。

"(?<a>\\w+)-(?<b>\\w+)-(?:(?<c>\\w+)" \
"(?:-(?<d>\\w+)(?:-(?<e>\\w+)(?:-(?<f>\\w+)" \
"(?:-(?<g>\\w+)(?:-(?<h>\\w+)(?:-(?<i>\\w+)" \
"(?:-(?<j>\\w+))?" \
")?)?)?" \
")?)?)?" \
")"

それは良い考えですか?いいえ、そうは思いません。

于 2013-03-14T23:06:41.463 に答える
0

そのようにできるかどうかはわかりませんが、グローバル フラグを使用して、コロン間のすべての単語を見つけることができます。以下を参照してください。

http://regex101.com/r/gK0lX1

ただし、グループの数を自分で検証する必要があります。グローバル フラグがないと、すべての一致ではなく、1 つの一致のみが取得されます。変更{3,10}する{1,5}と、代わりに「sir」という結果が得られます。

import re

s = "start:test-test-lorem-ipsum-sir-doloret-etc-etc-something:end"
print re.findall(r"(\b\w+?\b)(?:-|:end)", s)

生産する

['test', 'test', 'lorem', 'ipsum', 'sir', 'doloret', 'etc', 'etc', 'something']

于 2013-03-07T17:35:08.583 に答える
0

組み合わせると:

  1. あなたの観察: 単一のキャプチャ グループのどのような種類の繰り返しでも、最後のキャプチャが上書きされるため、キャプチャ グループの最後のキャプチャのみが返されます。
  2. 知識: 全体ではなく部分に基づいてキャプチャを行うと、正規表現エンジンが繰り返す回数に制限を設定することができなくなります。制限はメタデータでなければなりません (正規表現ではありません)。
  3. 回答にプログラミング(ループ)を含めることはできないという要件があり、質問で行ったようにキャプチャグループを単にコピーして貼り付けるだけの回答もありません。

できないと推測できます。

更新:いくつかの正規表現エンジンがあります。1が必ずしも正しいとは限りません。その場合、指定した正規表現が機能しますstart:(?:(\w+)-?){3,10}:end( source )。

于 2013-03-08T21:10:13.947 に答える