4

次の入力があるとしましょう。

<amy>
(bob)
<carol)
(dean>

次の正規表現もあります。

<(\w+)>|\((\w+)\)

これで、2つの一致が得られます(rubular.comで見られるように):

  • <amy>一致し、\1キャプチャしamy\2失敗します
  • (bob)一致し、\2キャプチャしbob\1失敗します

この正規表現は、私たちが望むことのほとんどを実行します。

  • 開閉ブラケットと適切に一致します(つまり、混合なし)
  • 興味のある部分をキャプチャします

ただし、いくつかの欠点があります。

  • キャプチャパターン(つまり「メイン」部分)が繰り返されます
    • この場合のみ\w+ですが、一般的に言えば、これは非常に複雑になる可能性があります。
      • 後方参照が含まれる場合は、代替ごとに番号を付け直す必要があります。
      • 繰り返しはメンテナンスを悪夢にします!(変更された場合はどうなりますか?)
  • グループは本質的に複製されます
    • どの代替一致に応じて、異なるグループにクエリを実行する必要があります
      • それは唯一\1または\2この場合ですが、一般的に「メイン」部分は独自のキャプチャグループを持つことができます!
    • これは不便であるだけでなく、これが実行できない場合もあります(たとえば、1つのグループのみのクエリに制限されているカスタム正規表現フレームワークを使用している場合)。
  • {...}[...]なども一致させたい場合、状況は急速に悪化します。

したがって、問題は明らかです。「メイン」パターンを繰り返さずにこれを行うにはどうすればよいでしょうか。

注:ほとんどの場合、私はjava.util.regexフレーバーに興味がありますが、他のフレーバーも歓迎します。


付録

このセクションには何も新しいことはありません。上記の問題を例で示しているだけです。

上記の例を次のステップに移しましょう。これらを一致させたいと思います。

<amy=amy>
(bob=bob)
[carol=carol]

しかし、これらではありません:

<amy=amy)   # non-matching bracket
<amy=bob>   # left hand side not equal to right hand side

別の手法を使用すると、次のように機能します(rubular.comで見られるように)。

<((\w+)=\2)>|\(((\w+)=\4)\)|\[((\w+)=\6)\]

上で説明したように:

  • メインパターンを単純に繰り返すことはできません。後方参照の番号を付け直す必要があります
  • 繰り返しは、それが変更された場合のメンテナンスの悪夢も意味します
  • どの代替一致に応じて、、、またはのいずれかをクエリする必要が\1 \2あり\3 \4ます\5 \6
4

6 に答える 6

5

先読みを使用して、実際の一致を行う前にグループ番号を「ロックイン」することができます。

String s = "<amy=amy>(bob=bob)[carol=carol]";
Pattern p = Pattern.compile(
  "(?=[<(\\[]((\\w+)=\\2))(?:<\\1>|\\(\\1\\)|\\[\\1\\])");
Matcher m = p.matcher(s);

while(m.find())
{
  System.out.printf("found %s in %s%n", m.group(2), m.group());
}

出力:

found amy in <amy=amy>
found bob in (bob=bob)
found carol in [carol=carol]

それでも地獄のように醜いですが、変更を加えるたびにすべてのグループ番号を再計算する必要はありません。たとえば、中括弧のサポートを追加するには、次のようにします。

"(?=[<(\\[{]((\\w+)=\\2))(?:<\\1>|\\(\\1\\)|\\[\\1\\]|\\{\\1\\})"
于 2010-07-02T14:07:12.913 に答える
3

preg(Perl Regexライブラリ)では、これはあなたの例と一致\3し、内部をキャッチします:

((<)|\()(\w+)(?(2)>|\))

ただし、JSでは機能しません-方言を指定していません...

(?(2)...|...)これは、基本的にnull以外のキャプチャであるかどうかを示す条件演算子に依存し2、パイプの前で一致し、そうでない場合はパイプの後で一致します。この形式では、パイプは交互(「または」)ではありません。

更新申し訳ありませんが、私はJavaビットを完全に見逃しました:)とにかく、どうやらJavaは条件付き構造をサポートしていません。そして、私はそれについて他にどのように行くのか分かりません:(

また、付録の場合(方言が間違っていても):

(?:(<)|(\()|\[)(\w+)=\3(?(1)>|(?(2)\)|]))

名前が再び入ってい\3ます(最初のキャプチャパレンを削除しましたが、1つの追加のオープニングパレンチェックのために別のパレンを追加する必要がありました)

于 2010-07-02T12:54:27.000 に答える
3

私が思いついた唯一の解決策は、さまざまな代替で空の文字列をキャプチャする手法に触発されています。後でこれらのグループを逆参照すると、疑似条件として機能します。

したがって、このパターンは2番目の例(rubular.comで見られるように)で機能します。

                  __main__
                 /        \
(?:<()|\(()|\[())((\w+)=\5)(\1>|\2\)|\3\])
\_______________/          \_____________/
    \1   \2   \3

したがって、基本的に各開始ブラケットに対して、空の文字列をキャプチャするグループを割り当てます。次に、閉じ括弧を一致させようとすると、どのグループが成功したかを確認し、対応する閉じ括弧を一致させます。

「メイン」部分を繰り返す必要はありませんが、Javaでは、後方参照の番号を付け直す必要がある場合があります。これは、名前付きグループをサポートするフレーバーでは問題になりません。

于 2010-07-02T13:14:31.407 に答える
0

Perlでのこの例はあなたの興味を引くかもしれません:

$str = q/<amy=amy> (bob=bob) [carol=carol] <amy=amy) <amy=bob>/;
$re = qr/(?:<((\w+)=\2)>|\(((\w+)=\4)\)|\[((\w+)=\6)\])+/;
@list = ($str =~ /$re/g);
for(@list) {
    say $i++," = ",$_;
}

私はあなたの正規表現を(?:regex)+で囲みます

于 2010-07-02T13:11:00.207 に答える
0

このような場合、単一の正規表現を使用することはばかげた制限であり、複数の正規表現を使用することへの「メンテナンスの悪夢」に同意しません。類似しているが異なる表現を数回繰り返すことは、より多くの可能性があります。単一の過度に複雑な正規表現よりも、保守可能(まあ、保守不可能ではない)であり、おそらくパフォーマンスもさらに優れています。

しかし、とにかく、変数を使用して正規表現を構成するだけであれば、繰り返しはありません。

ここにいくつかの擬似コードがあります:

Brackets = "<>,(),[]"
CoreRegex = "(\w+)=\1"

loop CurBracket in Brackets.split(',')
{
    Input.match( Regex.quote(CurBracket.left(1)) & CoreRegex & Regex.quote(CurBracket.right(1)) )
}


(これは一般的な考え方を示すためだけのものです。実際の実装では、ブラケットセットに既にエスケープされた配列を使用する可能性があります)。

于 2010-07-02T13:02:13.457 に答える
0

この正規表現を手動で作成する簡単な方法がないと仮定して、それをコンピューターに任せてみませんか?以下のような関数を使用できます(ここでは、Javaよりも正規表現に少し慣れているのでC#構文を使用していますが、Javaに適応させるのはそれほど難しくありません)。

読者への演習として、 AdaptBackreferences()関数を多かれ少なかれ実装しないままにしたことに注意してください。後方参照の番号付けを調整するだけです。

    struct BracketPair {public string Open; public string Close;};

    static string[] MatchTextInBrackets(string text, string innerPattern, BracketPair[] bracketPairs) {
        StringBuilder sb  = new StringBuilder();

        // count number of catching parentheses of innerPattern here:
        int numberOfInnerCapturingParentheses = Regex.Match("", innerPattern).Groups.Count - 1;

        bool firstTime = true;
        foreach (BracketPair pair in bracketPairs) {
            // apply logic to change backreference numbering:
            string adaptedInnerPattern = AdaptBackreferences(innerPattern);
            if (firstTime) { firstTime = false; } else { sb.Append('|'); }
            sb.Append(pair.Open).Append("(").Append(adaptedInnerPattern).Append(")").Append(pair.Close);
        }
        string myPattern = sb.ToString();
        MatchCollection matches = Regex.Matches(text, myPattern);
        string[] result = new string[matches.Count];
        for(int i=0; i < matches.Count; i++) {
            StringBuilder mb = new StringBuilder();
            for(int j=0; j < bracketPairs.Length; j++) {
                mb.Append(matches[i].Groups[1 + j * (numberOfInnerCapturingParentheses + 1)]); // append them all together, assuming all exept one are empty
            }
            result[i] = mb.ToString();
        }
        return result;
    }

    static string AdaptBackreferences(string pattern) { return pattern; } // to be written
于 2010-07-02T14:11:51.497 に答える