4

假设我们有以下输入:

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

我们还有以下正则表达式:

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

现在我们得到两个匹配项(如 rubular.com 上所见):

  • <amy>是匹配,\1捕获amy\2失败
  • (bob)是匹配,\2捕获bob\1失败

这个正则表达式完成了我们想要的大部分工作,它们是:

  • 它正确匹配左括号和右括号(即没有混合)
  • 它捕获了我们感兴趣的部分

但是,它确实有一些缺点:

  • 重复捕获模式(即“主要”部分)
    • \w+在这种情况下,但一般来说,这可能非常复杂,
      • 如果它涉及反向引用,则必须为每个替代项重新编号!
      • 重复使维护成为噩梦!(如果它改变了呢?)
  • 这些组基本上是重复的
    • 根据哪些备用匹配,我们必须查询不同的组
      • 它只是\1\2在这种情况下,但通常“主要”部分可以拥有自己的捕获组!
    • 这不仅不方便,而且在某些情况下这是不可行的(例如,当我们使用仅限于查询一组的自定义正则表达式框架时)
  • 如果我们还想匹配{...},[...]等,情况会迅速恶化。

所以问题很明显:我们如何在不重复“主要”模式的情况下做到这一点?

注意:在大多数情况下,我对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)...|...),它基本上说如果2是非空捕获,则在管道之前匹配,否则在管道之后匹配。在这种形式中,管道不是交替(“或”)。

更新对不起,我完全错过了Java位:)无论如何,显然Java不支持条件构造;而且我不知道我该怎么做:(

此外,对于您的附录(即使它是错误的方言):

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

该名称再次出现\3(我摆脱了第一个捕获括号,但我必须添加另一个以进行额外的打开括号检查)

于 2010-07-02T12:54:27.000 回答
3

我能想出的唯一解决方案是受到在不同备用上捕获空字符串的技术的启发;稍后对这些组的反向引用可以用作伪条件。

因此,此模式适用于第二个示例(如 rubular.com 上所示):

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

所以基本上对于每个左括号,我们分配一个捕获空字符串的组。然后当我们尝试匹配右括号时,我们会看到哪个组成功,并匹配相应的右括号。

“主要”部分不必重复,但在 Java 中,反向引用可能必须重新编号。这在支持命名组的风味中不会成为问题。

于 2010-07-02T13:14:31.407 回答
0

当你得到这样的东西时,使用单个正则表达式是一个愚蠢的限制,我根本不同意你的“维护噩梦”使用多个 - 多次重复类似但不同的表达式可能会更多与单个过于复杂的正则表达式相比,可维护(嗯,不易维护),甚至可能还有更好的性能。

但无论如何,如果您只使用变量来编写您的正则表达式,就没有重复。

这是一些伪代码:

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

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


(ps这只是为了给出一般的想法——我可能会在实际实现中使用已经转义的数组作为括号集)。

于 2010-07-02T13:02:13.457 回答
0

假设没有简单的方法手动编写这个正则表达式,为什么不把它留给计算机呢?你可以有一个函数,可能如下所示(我在这里使用 C# 语法,因为我在这里比在 Java 中更熟悉正则表达式,但它应该不会太难适应 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 回答
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 回答