web-dev-qa-db-ja.com

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

私は二重中括弧内にデータを取得する方法についての質問を読んでいて( この質問 )、その後誰かが平衡化グループを育てました。それらが何であり、どのように使用するかはまだよくわかりません。

Balancing Group Definition を読みましたが、説明を追うのは難しく、私が言及した質問についてはまだかなり混乱しています。

平衡化グループとは何か、それらがどのように役立つかを簡単に説明できますか?

85
It'sNotALie.

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

余談:繰り返しグループ

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

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

(.)+

および文字列"abcd"

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

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

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

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

m.Groups[1].Captures

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

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"

これにより、式のさまざまな部分から単一のスタックに物事をプッシュすることさえできます。ただし、これは、このCaptureCollectionにリストされている複数のキャプチャを追跡できるという.NETの機能です。しかし、このコレクションはstackであると言いました。それではpopそこからのことはできますか?

入力:バランシンググループ

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

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

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

ただし、マイナス構文にはもう1つ詳細があります。スタックが既に空の場合、グループは(サブパターンに関係なく)失敗します。この動作を活用して、ネストレベルをカウントすることができます。これが、名前バランシンググループの由来となる場所です(そして、興味深い場所になります)。正しく括弧で囲まれた文字列に一致させたいとします。スタック上の各開き括弧をプッシュし、閉じ括弧ごとに1つのキャプチャをポップします。 1つの閉じ括弧が多すぎると、空のスタックがポップされ、パターンが失敗します。

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

そのため、繰り返しの選択肢が3つあります。最初の選択肢は、括弧以外のすべてを消費します。 2番目の選択肢は、(sと一致し、それらをスタックにプッシュします。 3番目の選択肢は、)sに一致し、スタックから要素をポップします(可能な場合!)。

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

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

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

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

(?(condition)truePattern|falsePattern)

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

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

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

この条件付き構文は、バランスグループとは何の関係もありませんが、グループの全機能を活用する必要があることに注意してください。

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

補遺:(?<A-B>...)構文とは何ですか?

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

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

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

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

コビはこれを提供しました Live-Demo 彼の答え

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

  • Arbitrarily意的に多くのキャプチャを記憶する
  • 入れ子構造の検証
  • 各ネストレベルをキャプチャする

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

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

166
Martin Ender

M. Buettnerの優れた答えへのほんの小さな追加:

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

(?<A-B>x)(?<-A>(?<B>x))と微妙に異なります。それらは同じ制御フローをもたらします*、しかしcaptureが異なります。
たとえば、バランスの取れた中括弧のパターンを見てみましょう。

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

マッチの終わりにはバランスの取れた文字列がありますが、それだけです-わかりませんwhereBスタックが空です。エンジンが私たちのためにしたハードワークはなくなりました。
正規表現ストームの例

(?<A-B>x)はその問題の解決策です。どうやって? Itdoes n'tx$Aにキャプチャします。以前のBのキャプチャと現在の位置の間のコンテンツをキャプチャします。

パターンで使用してみましょう。

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

これにより、途中のペアごとに、中括弧(およびその位置)の間の文字列が$Contentにキャプチャされます。
文字列{1 2 {3} {4 5 {6}} 7}には、364 5 {6}、および1 2 {3} {4 5 {6}} 7の4つのキャプチャがあります-nothingまたは}}}}
例-tableタブをクリックして、${Content}、captures

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

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

38
Kobi