贪婪运算符的结果在正面和负面前瞻中有所不同?

Greedy operator result is differ in positive and negative lookahead?

我对正面和负面前瞻的贪婪运算符感到困惑。

用于正面前瞻

的脚本
foreach (<DATA>){
$_ = m/AAA.+(?=BBB)/g;
print "$&\n";
} 
__DATA__
AAA 1121 BBB
AAA 443  CCC
AAA 4431 BBB
ABC 321  EACA
AAA 321  BBB
ACD 431 MAKN
AAA 751  ABC

输出

AAA 1121 

AAA 4431 

AAA 321  

负前瞻

foreach (<DATA>){
$_ = m/AAA.+(?!BBB)/g; 
print "$&\n";
} 

输出

AAA 1121 BBB
AAA 443  CCC
AAA 4431 BBB

AAA 321  BBB

AAA 751  ABC

在执行negative lookahed时不考虑(?!BBB)。因为我在 (?!BBB) 之前使用了贪心运算符。在那种情况下,积极的前瞻贪婪运算符考虑 (?=BBB)。这就是为什么给出不同的结果?

我可以通过代码$_ = m/AAA\s\d+(?!.+BBB)/g;轻松实现OP。

但是我不知道我的代码执行的是什么?

让我们考虑第一个例子:

AAA 1121 BBB
\_/\_______/^
 |     |    |
 |     |    +--- this (the empty string right there) satisfies (?!BBB)
 |     |
 |     +-------- matched by .+
 |     
 +-------------- matched by AAA

这是因为贪心.+消耗1121 BBB包括BBB。在它消耗完该行的其余部分后,将根据剩余的空字符串检查 (?!BBB)。这个空字符串满足 (?!BBB) 因为它后面没有 BBB?


否定前瞻

算法执行如下。 ^ 当前位置 (字符串中有一个当前位置,模式中有一个当前位置 (kind of)).

  1. 初始状态:

    AAA 1121 BBB          AAA.+(?!BBB)
    ^                     ^
    
  2. 匹配AAA

    AAA 1121 BBB          AAA.+(?!BBB)
       ^                     ^
    
  3. 匹配.+

    AAA 1121 BBB          AAA.+(?!BBB)
                ^              ^
    
  4. 勾选(?!BBB)

    AAA 1121 BBB          AAA.+(?!BBB)
                ^                     ^
    
  5. 在此位置没有 BBB 匹配 => 成功!

    AAA 1121 BBB
    \__________/
    

正面前瞻

现在,让我们看看为什么 完全 AAA.+(?=BBB) 会产生匹配:

  1. 初始状态:

    AAA 1121 BBB          AAA.+(?=BBB)
    ^                     ^
    
  2. 匹配AAA

    AAA 1121 BBB          AAA.+(?=BBB)
       ^                     ^
    
  3. 匹配.+

    AAA 1121 BBB          AAA.+(?=BBB)
                ^              ^
    
  4. 勾选(?=BBB)

    AAA 1121 BBB          AAA.+(?=BBB)
                ^              ^
    

    在此位置没有 BBB 匹配 => 回溯(.+ 少消耗一个字符)

  5. 勾选(?=BBB)

    AAA 1121 BBB          AAA.+(?=BBB)
               ^               ^
    

    在此位置没有 BBB 匹配 => 回溯(.+ 少消耗一个字符)

  6. 勾选(?=BBB)

    AAA 1121 BBB          AAA.+(?=BBB)
              ^                ^
    

    在此位置没有 BBB 匹配 => 回溯(.+ 少消耗一个字符)

  7. 勾选(?=BBB)

    AAA 1121 BBB          AAA.+(?=BBB)
             ^                        ^
    
  8. 我们在这个位置确实有一个 BBB 匹配 => 成功!

    AAA 1121 BBB
    \_______/
    

在你的两种情况下它的工作方式没有区别,并且.+在你的两种情况下都是贪婪的。

在匹配AAA.+(?=BBB)AAA 1121 BBB时,最多.+可以匹配AAA之后的<spc>1121<spc>。任何更长的时间都会导致 (?=BBB) 失败。

在匹配AAA.+(?!BBB)AAA 1121 BBB时,最多.+可以匹配AAA之后的<spc>1121<spc>BBB。作为字符串的其余部分,它不可能再匹配任何内容。

请注意,字符串末尾后没有 BBB,因此 (?!BBB) 匹配字符串末尾。


(?:(?!STRING).)* 之于 STRING 正如 [^CHAR]* 之于 CHAR.

我会选择

say  if /^(AAA\s+\S+)\s+(?:(?!BBB)\s)*\z/;

再三考虑,我会选择

my @F = split;
say "$F[0] $F[1]" if $F[0] eq 'AAA' && $F[2] ne 'BBB';