Regex 我们如何匹配a^n b^n?

Regex 我们如何匹配a^n b^n?,regex,regex-lookarounds,capturing-group,nested-reference,Regex,Regex Lookarounds,Capturing Group,Nested Reference,这是教育正则表达式系列文章的第二部分。它展示了如何使用lookaheads和嵌套引用来匹配非规则语言anbn。嵌套引用首先在中介绍: 其中一个典型的非冲突因素是: L={anbn:n>0} 这是所有非空字符串的语言,由一定数量的a,后跟相等数量的b。这种语言中的字符串示例有ab,aabb,aaabbb 此语言可以通过以下方式显示为非规则语言。它实际上是一个原型,可以由S生成→ aSb | ab 尽管如此,现代的正则表达式实现显然不仅仅识别常规语言。也就是说,它们不是形式语言理论定义的“规则”。P

这是教育正则表达式系列文章的第二部分。它展示了如何使用lookaheads和嵌套引用来匹配非规则语言anbn。嵌套引用首先在中介绍:

其中一个典型的非冲突因素是:

L={a
n
b
n
:n>0}

这是所有非空字符串的语言,由一定数量的
a
,后跟相等数量的
b
。这种语言中的字符串示例有
ab
aabb
aaabbb

此语言可以通过以下方式显示为非规则语言。它实际上是一个原型,可以由
S生成→ aSb | ab

尽管如此,现代的正则表达式实现显然不仅仅识别常规语言。也就是说,它们不是形式语言理论定义的“规则”。PCRE和Perl支持递归正则表达式,.NET支持平衡组定义。甚至更少的“花哨”特性,例如反向引用匹配,意味着正则表达式不是规则的

但这种“基本”功能到底有多强大?例如,我们可以用Java正则表达式识别
L
?我们是否可以将lookarounds和嵌套引用结合起来,并使用一种模式来匹配字符串,例如
ab
aabb
aaabbb
,等等

工具书类
  • -及
关联问题

    • 答案是,不用说,是的!您完全可以编写一个Java正则表达式模式来匹配anbn。它对断言使用正向前瞻,对“计数”使用嵌套引用

      这个答案不是立即给出模式,而是引导读者完成推导过程。在缓慢构造解决方案时,会给出各种提示。在这方面,希望这个答案不仅仅包含另一个整洁的正则表达式模式。希望读者也能学会如何“在正则表达式中思考”,以及如何将各种结构和谐地组合在一起,以便他们将来能够自己推导出更多的模式

      由于其简洁性,用于开发解决方案的语言将是PHP。模式最终确定后的最终测试将在Java中完成


      步骤1:前瞻断言 让我们从一个更简单的问题开始:我们希望在字符串开头匹配
      a+
      ,但前提是后面紧跟
      b+
      。我们可以使用
      ^
      进行匹配,因为我们只想匹配
      a+
      而不匹配
      b+
      ,所以我们可以使用断言
      (?=…)

      以下是我们使用简单测试线束的模式:

      function testAll($r, $tests) {
         foreach ($tests as $test) {
            $isMatch = preg_match($r, $test, $groups);
            $groupsJoined = join('|', $groups);
            print("$test $isMatch $groupsJoined\n");
         }
      }
       
      $tests = array('aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb');
       
      $r1 = '/^a+(?=b+)/';
      #          └────┘
      #         lookahead
      
      testAll($r1, $tests);
      
      输出为():

      这正是我们想要的输出:我们匹配
      a+
      ,前提是它位于字符串的开头,并且紧跟
      b+

      课程:您可以使用lookarounds中的模式进行断言


      步骤2:以前瞻模式捕获(和f r e-s p c i n g模式) 现在让我们假设,即使我们不希望
      b+
      成为比赛的一部分,我们还是希望它进入第1组。另外,由于我们预期会有一个更复杂的模式,所以让我们使用
      x
      修饰符,以便使正则表达式更具可读性

      在前面的PHP代码段的基础上,我们现在有以下模式:

      $r2 = '/ ^ a+ (?= (b+) ) /x';
      #             │   └──┘ │
      #             │     1  │
      #             └────────┘
      #              lookahead
       
      testAll($r2, $tests);
      
      现在输出为():

      请注意,例如,
      aaa | b
      join
      -ing每个组用
      “|”捕获的内容的结果。在这种情况下,组0(即模式匹配的内容)捕获了
      aaa
      ,组1捕获了
      b

      课程:您可以捕获周围环境中的内容。您可以使用自由间距来增强可读性


      步骤3:将前瞻重构为“循环” 在引入计数机制之前,我们需要对模式进行一次修改。目前,前瞻在
      +
      重复“循环”之外。到目前为止这还不错,因为我们只是想断言在我们的
      a+/code>之后有一个
      b+/code>,但我们最终真正想做的是断言,对于我们在“循环”中匹配的每个
      a
      ,都有一个相应的
      b

      现在我们不必担心计数机制,只需按照以下步骤进行重构:

      • 首先将
        a+
        重构为
        (?:a)+
        (请注意,
        (?:…)
        是一个非捕获组)
      • 然后在该非捕获组内移动前瞻
        • 请注意,我们现在必须先“跳过”
          a*
          ,然后才能“看到”
          b+
          ,因此相应地修改模式
      因此,我们现在有以下几点:

      $r3 = '/ ^ (?: a (?= a* (b+) ) )+ /x';
      #          │     │      └──┘ │ │
      #          │     │        1  │ │
      #          │     └───────────┘ │
      #          │       lookahead   │
      #          └───────────────────┘
      #           non-capturing group
      
      输出与前面()相同,因此在这方面没有变化。重要的是,现在我们正在
      +
      循环的每次迭代中进行断言。在我们当前的模式下,这是不必要的,但接下来我们将使用自引用为第1组“计数”

      课程:您可以在非捕获组内捕获。环顾四周可以重复


      步骤4:这是我们开始计数的步骤 下面是我们要做的:我们将重写第1组,以便:

      • +
        的第一次迭代结束时,当第一次
        a
        匹配时,它应该捕获
        b
      • 在第二次迭代结束时,当另一个
        a
        匹配时,它应该捕获
        bb
      • 在第三次迭代结束时,它应该捕获
        bbb
      • 在第n次迭代结束时,第1组应捕获bn
      • 如果没有足够的
        b
        捕获到组1中,那么断言就会失败
      因此,组1现在是
      (b+)
      ,必须重写
      aaa 0
      aaab 1 aaa|b
      aaaxb 0
      xaaab 0
      b 0
      abbb 1 a|bbb
      
      $r3 = '/ ^ (?: a (?= a* (b+) ) )+ /x';
      #          │     │      └──┘ │ │
      #          │     │        1  │ │
      #          │     └───────────┘ │
      #          │       lookahead   │
      #          └───────────────────┘
      #           non-capturing group
      
      $tests = array(
        'aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb', 'aabb', 'aaabbbbb', 'aaaaabbb'
      );
       
      $r4 = '/ ^ (?: a (?= a* (\1? b) ) )+ /x';
      #          │     │      └─────┘ | │
      #          │     │         1    | │
      #          │     └──────────────┘ │
      #          │         lookahead    │
      #          └──────────────────────┘
      #             non-capturing group
      
      aaa 0
      aaab 1 aaa|b        # (*gasp!*)
      aaaxb 0
      xaaab 0
      b 0
      abbb 1 a|b          # yes!
      aabb 1 aa|bb        # YES!!
      aaabbbbb 1 aaa|bbb  # YESS!!!
      aaaaabbb 1 aaaaa|bb # NOOOOOoooooo....
      
       a a a a a b b b
      ↑
      # Initial state: Group 1 is "uninitialized".
                 _
       a a a a a b b b
        ↑
        # 1st iteration: Group 1 couldn't match \1 since it was "uninitialized",
        #                  so it matched and captured just b
                 ___
       a a a a a b b b
          ↑
          # 2nd iteration: Group 1 matched \1b and captured bb
                 _____
       a a a a a b b b
            ↑
            # 3rd iteration: Group 1 matched \1b and captured bbb
                 _
       a a a a a b b b
              ↑
              # 4th iteration: Group 1 could still match \1, but not \1b,
              #  (!!!)           so it matched and captured just b
                 ___
       a a a a a b b b
                ↑
                # 5th iteration: Group 1 matched \1b and captured bb
                #
                # No more a, + "loop" terminates
      
      $r5 = '/ ^ (?: a (?= a* (\1?+ b) ) )+ /x';
      #          │     │      └──────┘ │ │
      #          │     │          1    │ │
      #          │     └───────────────┘ │
      #          │         lookahead     │
      #          └───────────────────────┘
      #             non-capturing group
      
      aaa 0
      aaab 1 a|b          # Yay! Fixed!
      aaaxb 0
      xaaab 0
      b 0
      abbb 1 a|b
      aabb 1 aa|bb
      aaabbbbb 1 aaa|bbb
      aaaaabbb 1 aaa|bbb  # Hurrahh!!!
      
      $tests = array(
        'aaa', 'aaab', 'aaaxb', 'xaaab', 'b', 'abbb', 'aabb', 'aaabbbbb', 'aaaaabbb',
        '', 'ab', 'abb', 'aab', 'aaaabb', 'aaabbb', 'bbbaaa', 'ababab', 'abc',
        str_repeat('a', 5000).str_repeat('b', 5000)
      );
       
      $r6 = '/ ^ (?: a (?= a* (\1?+ b) ) )+ \1 $ /x';
      #          │     │      └──────┘ │ │
      #          │     │          1    │ │
      #          │     └───────────────┘ │
      #          │         lookahead     │
      #          └───────────────────────┘
      #             non-capturing group
      
      public static void main(String[] args) {
       
              String aNbN = "(?x) (?:  a  (?= a* (\\1?+ b))  )+ \\1";
              String[] tests = {
                      "",      // false
                      "ab",    // true
                      "abb",   // false
                      "aab",   // false
                      "aabb",  // true
                      "abab",  // false
                      "abc",   // false
                      repeat('a', 5000) + repeat('b', 4999), // false
                      repeat('a', 5000) + repeat('b', 5000), // true
                      repeat('a', 5000) + repeat('b', 5001), // false
              };
              for (String test : tests) {
                      System.out.printf("[%s]%n  %s%n%n", test, test.matches(aNbN));
              }
       
      }
       
      static String repeat(char ch, int n) {
              return new String(new char[n]).replace('\0', ch);
      }
      
      function is_anbn($s) {
         return (preg_match('/^(a+)(b+)$/', $s, $groups)) &&
            (strlen($groups[1]) == strlen($groups[2]));
      }
      
      $rRecursive = '/ ^ (a (?1)? b) $ /x';
      
      /^(a(?1)?b)$/
      
      ^
        (?<A>a)+
        (?<B-A>b)+  (?(A)(?!))
        (?<C-B>c)+  (?(B)(?!))
        ...
        (?<Z-Y>z)+  (?(Y)(?!))
      $
      
      ^
        (?=(a(?-1)?b))  a+
        (?=(b(?-1)?c))  b+
        ...
        (?=(x(?-1)?y))  x+
           (y(?-1)?z)
      $