代码之家  ›  专栏  ›  技术社区  ›  polygenelubricants

使用同一组捕获<thisPartOnly>和(thisPartOnly)

  •  4
  • polygenelubricants  · 技术社区  · 14 年前

    假设我们有以下输入:

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

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

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

    as seen on rubular.com

    • <amy> 是一场比赛, \1 捕获 amy , \2 失败
    • (bob) 是一场比赛, 捕获 bob , \1

    这个正则表达式实现了我们想要的大部分功能,包括:

    • 正确匹配开闭式支架(即不混合)

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

    • 重复捕获模式(即“主要”部分)
      • 只是 \w+
        • 如果涉及反向引用,则必须为每个备用项重新编号!
    • 这些组基本上是重复的
      • 根据哪些备选匹配项,我们必须查询不同的组
        • 只是 \2 在这种情况下,但一般的“主要”部分可以有自己的捕获组!
      • 这不仅不方便,而且可能存在不可行的情况(例如,当我们使用仅限于查询一个组的自定义regex框架时)
    • {...} , [...] 等等。

    我们如何做到这一点而不重复“主要”模式?

    注:我最感兴趣的是 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
    

    使用另一种技术,我们有以下几种方法( as seen on rubular.com ):

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

    • 主要模式不能简单地重复;反向引用必须重新编号
    • 重复也意味着维护噩梦,如果它曾经改变
    • 根据哪个备选匹配项,我们必须查询 \1 \2 \3 \4 ,或 \5 \6
    6 回复  |  直到 14 年前
        1
  •  5
  •   Alan Moore Chris Ballance    14 年前

    在进行真正的匹配之前,可以使用lookahead来“锁定”组号。

    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\\})"
    
        2
  •  3
  •   Amadan    14 年前

    在preg(Perl Regex库)中,这将匹配您的示例,并且 \3

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

    它取决于条件运算符 (?(2)...|...) 2

    更新

    另外,对于你的阑尾(即使它是错误的方言):

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

    这个名字又出现了 \3 (我去掉了第一个捕获的paren,但我不得不为一个额外的打开paren检查添加另一个)

        3
  •  3
  •   polygenelubricants    14 年前

    我能想到的唯一解决办法是,用不同的交替方式捕捉空字符串的技术;稍后对这些组的反向引用可以用作伪条件。

    因此,此模式适用于第二个示例( as seen on rubular.com

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

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

    “main”部分不必重复,但在Java中,backreference可能需要重新编号。这在支持命名组的风格中不会是问题。

        4
  •  0
  •   Toto    14 年前

    $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++," = ",$_;
    }
    

        5
  •  0
  •   Peter Boughton    14 年前

    当你遇到这样的情况时,使用一个regex是一个愚蠢的限制,我根本不同意你使用多个regex的“维护噩梦”——重复几次类似但不同的表达式很可能是错误的 更多 与一个过于复杂的正则表达式相比,它是可维护的(好吧,不那么不可维护),甚至可能有更好的性能。

    下面是一些伪代码:

    Brackets = "<>,(),[]"
    CoreRegex = "(\w+)=\1"
    
    loop CurBracket in Brackets.split(',')
    {
        Input.match( Regex.quote(CurBracket.left(1)) & CoreRegex & Regex.quote(CurBracket.right(1)) )
    }
    


    (顺便说一句,这只是给出一个大概的想法——在实际实现中,我可能会对括号集使用已经转义的数组)。

        6
  •  0
  •   Frank    14 年前

    您可以有一个函数,可能如下所示(我在这里使用C#语法,因为我在这里比在Java中更熟悉regex,但将其改编为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