正規表現バランスグループとは何ですか?


91

二重中括弧内にデータを取得する方法についての質問を読んでいました(この質問)を誰かがグループのバランスをとってきました。それらがどのようなもので、どのように使用するのかはまだよくわかりません。

読み通した Balancing Group Definitionが、説明を理解するのは難しく、私が述べた質問にはまだかなり混乱しています。

誰かが単純にバランスグループとは何か、およびそれらがどのように役立つかを説明できますか?


これが実際にサポートされている正規表現エンジンの数について知りたいです。
Mike de Klerk 2013年

2
@MikedeKlerk少なくとも.NET Regexエンジンでサポートされています。
それはNotALieです。

回答:


173

私の知る限りでは、バランスグループは.NETの正規表現に特有のものです。

脇:繰り返しグループ

まず、.NETが(ここでも、私の知る限り)単一のキャプチャグループの複数のキャプチャにアクセスできる正規表現の唯一の種類であることを知っておく必要があります(後方参照ではなく、一致が完了した後)。

これを例で説明するために、次のパターンを考えます。

(.)+

と文字列"abcd"

他のすべての正規表現フレーバーでは、キャプチャグループ1は単に1つの結果を生成しますd(注、完全一致はもちろんabcd期待どおりです)。これは、キャプチャグループを新しく使用するたびに、以前のキャプチャが上書きされるためです。

一方、.NETはそれらすべてを記憶します。そしてそれはスタックでそうします。上記の正規表現のように一致した後

Match m = new Regex(@"(.)+").Match("abcd");

あなたはそれを見つけるでしょう

m.Groups[1].Captures

あるCaptureCollection要素が4つのキャプチャに対応

0: "a"
1: "b"
2: "c"
3: "d"

ここで、番号はへのインデックスCaptureCollectionです。したがって、基本的にグループが再び使用されるたびに、新しいキャプチャがスタックにプッシュされます。

名前付きのキャプチャグループを使用している場合は、さらに興味深いものになります。.NETでは同じ名前を繰り返し使用できるため、次のような正規表現を記述できます。

(?<word>\w+)\W+(?<word>\w+)

2つの単語を同じグループにキャプチャします。この場合も、特定の名前のグループが見つかるたびに、キャプチャがスタックにプッシュされます。したがって、この正規表現を入力に適用して"foo bar"検査する

m.Groups["word"].Captures

2つのキャプチャを見つけます

0: "foo"
1: "bar"

これにより、式のさまざまな部分から単一のスタックに物をプッシュすることもできます。ただし、これは、このに記載されている複数のキャプチャを追跡できる.NETの機能にすぎませんCaptureCollection。しかし、私は言った、このコレクションはスタックです。だから それから物をポップすることができますか?

入力:グループの分散

できることがわかりました。のようなグループを使用する場合、部分式が一致(?<-word>...)すると、最後のキャプチャがスタックからポップされます。したがって、前の式をword...

(?<word>\w+)\W+(?<-word>\w+)

次に、2番目のグループが最初のグループのキャプチャをポップCaptureCollectionし、最後に空を受け取ります。もちろん、この例はほとんど役に立ちません。

ただし、マイナス構文にはもう1つ詳細があります。スタックが既に空の場合、グループは(そのサブパターンに関係なく)失敗します。この振る舞いを利用して、ネストレベルをカウントできます。これが、名前分散グループの由来(および興味深いところ)です。正しく括弧で囲まれた文字列を照合したいとします。左括弧をスタックにプッシュし、右括弧ごとに1つのキャプチャをポップします。1つの閉じ括弧が多すぎると、空のスタックがポップされ、パターンが失敗します。

^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$

したがって、3つの選択肢があります。最初の選択肢は、括弧ではないすべてのものを消費します。2番目の選択肢(は、スタックにプッシュするときにsに一致します。3番目の選択肢)は、スタックから要素をポップするときにsに一致します(可能な場合)。

注:明確にするために、一致しない括弧がないことを確認するだけです!つまり、括弧はまだ構文的に有効であるため、括弧をまったく含まない文字列一致します(括弧を一致させる必要がある構文では)。少なくとも1組の括弧を確保する場合は、の(?=.*[(])直後に先読みを追加し^ます。

ただし、このパターンは完全ではありません(または完全に正しいものではありません)。

フィナーレ:条件付きパターン

もう1つ注意点があります。これにより、文字列の終わりでスタックが空になることが保証されません(したがって、 (foo(bar)注意点があります。これ、有効です)。.NET(および他の多くのフレーバー)には、ここで私たちを支援するもう1つの構成要素、条件付きパターンがあります。一般的な構文は

(?(condition)truePattern|falsePattern)

ここで、falsePatternはオプションです-省略した場合、false-caseは常に一致します。条件は、パターンまたはキャプチャグループの名前のいずれかです。ここでは後者のケースに焦点を当てます。キャプチャグループの名前のtruePattern場合、その特定のグループのキャプチャスタックが空でない場合にのみ使用されます。つまり、(?(name)yes|no)name(スタック上にある)何かに一致してキャプチャした場合、パターンを使用する」などの条件付きパターンyes場合はパターンを使用しそれ以外の場合はパターンをno

したがって、上記のパターンの最後に(?(Open)failPattern)Open-stackが空でない場合にパターン全体が失敗するようなものを追加できます。パターンを無条件に失敗させる最も簡単な方法は、(?!)(空の否定先読み)です。これで最終的なパターンができました。

^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$

この条件付き構文自体は、バランスグループとは何の関係もありませんが、グループの能力を最大限に活用する必要があります。

ここからは空が限界です。多くの非常に洗練された使用法が可能であり、可変長の後読みなどの.NET-Regex機能(私は自分で難しい方法を学ぶ必要があった)と組み合わせて使用​​すると、いくつかの落とし穴があります。ただし、主な問題は常に次のとおりです。これらの機能を使用しても、コードは引き続き保守可能ですか?あなたはそれを本当によく文書化する必要があり、それに取り組むすべての人がこれらの機能も知っていることを確認してください。そうでない場合は、文字列を文字ごとに手動で歩き、整数のネストレベルを数えるだけの方がよいでしょう。

補遺:(?<A-B>...)構文には何がありますか?

この部分のクレジットはKobiに送られます(詳細については、以下の彼の回答を参照してください)。

上記のすべてで、文字列が正しく括弧で囲まれていることを検証できます。しかし、これらのすべての括弧の内容の(ネストされた)キャプチャを実際に取得できれば、はるかに便利です。もちろん、空ではない別のキャプチャスタックでかっこを開いたり閉じたりして、その位置に基づいて別の手順で部分文字列の抽出を行うことを覚えています。

しかし、.NETはここでもう1つの便利な機能を提供します。使用すると(?<A-B>subPattern)、スタックからキャプチャがポップされるだけでなくB、そのポップされたキャプチャBとこの現在のグループの間のすべてがスタックにプッシュされますA。したがって、このようなグループを閉じ括弧に使用する場合、スタックからネストレベルをポップしながら、ペアのコンテンツを別のスタックにプッシュすることもできます。

^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$

コビは彼の答えでこのライブデモを提供しました

したがって、これらすべてをまとめて、次のことができます。

  • 任意に多くのキャプチャを覚えている
  • ネストされた構造を検証する
  • 各ネストレベルをキャプチャする

すべて単一の正規表現で。それがエキサイティングでない場合...;)

それらについて初めて知ったときに役立つと思われるいくつかのリソース:


6
この回答は、「Advanced Regex-Fu」の下のStack Overflow Regular Expressions FAQに追加されました。
aliteralmind 2014

39

M.ブエトナーの優れた答えへのほんの少しの追加:

(?<A-B>)構文はどうなっていますか?

(?<A-B>x)は微妙に異なり(?<-A>(?<B>x))ます。結果は同じ制御フロー*になりますが、キャプチャーが異なります。
たとえば、中括弧のパターンを見てみましょう。

(?:[^{}]|(?<B>{)|(?<-B>}))+(?(B)(?!))

マッチの最後にはバランスのとれた文字列がありますが、それだけです- スタックが空であるため中括弧がどこにあるのかわかりませんB。エンジンが私たちのために行ったハードワークはなくなりました。
Regex Stormの例

(?<A-B>x)その問題の解決策です。どうやって?それはしませんキャプチャx$A:それは、以前のキャプチャ間のコンテンツ取り込みBと現在位置を。

私たちのパターンでそれを使ってみましょう:

(?:[^{}]|(?<Open>{)|(?<Content-Open>}))+(?(Open)(?!))

これにより$Content、途中のペアごとに、ブレース(およびその位置)の間の文字列がキャプチャされます。
文字列の場合{1 2 {3} {4 5 {6}} 7}があると思います4つのキャプチャ:364 5 {6}、および1 2 {3} {4 5 {6}} 7-より良いよりも} } } }
例- tableタブをクリックして${Content}、キャプチャ、

実際には、まったくバランスをとらずに使用できます(?<A>).(.(?<Content-A>).)。グループで区切られていても、最初の2文字をキャプチャします。
(ここでは先読みがより一般的に使用されますが、常にスケーリングするとは限りません。ロジックが重複する可能性があります。)

(?<A-B>)強力な機能です- キャプチャを正確に制御できます。パターンをさらに活用しようとするときは、このことを覚えておいてください。


@FYI、この質問に対する新しい回答で、気に入らなかっ質問からの議論を続けます。:)
zx81

文字列内のブレースをエスケープして、バランスブレースの正規表現チェックを実行する方法を見つけようとしています。たとえば、次のコードが渡されます:public class Foo {private const char BAR = '{'; プライベート文字列_qux = "{{{"; }誰かこれをやったことがありますか?
アンダーソン氏

@MrAnderson- |'[^']*'適切な場所に追加するだけです:。エスケープ文字も必要な場合は、ここに例があります(C#文字列リテラルを照合するための正規表現)[ stackoverflow.com/a/4953878/7586]
コビ、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.