Perl - 提取所有正则表达式匹配

Perl - Extract all regular expression match

$regex   = "[M]?[VI]?[A]?[R]?[G]?[D]?[LM]?[G]?[IVMAL]?[E]?";
$text = "VMVARGDLGVE";

if (@matched = $text =~ /$regex/g) {
    $no_of_match = scalar @matched;
    print "No of match: ", $no_of_match, "\n";
    foreach my $i (@matched) {
        print $i, "\n";
    }
}    

此程序正在生成如下输出: 匹配数:3 VMV ARGDLGVE

我期待这个程序输出如下内容: V 虚拟机 VMV MV MVA MVAR MVARG MVARGD MVARGDL MVARGDLG MVARGDLGV MVARGDLGVE ....

我试图将所有可能的匹配作为输出。如何获得所有可能的匹配项?

正则表达式匹配不是那样工作的。如果您在找到匹配项后再次搜索,它会从该匹配项的末尾 开始再次搜索。所以它找不到重叠的匹配项。

我假设您正在使用 Perl 5。我相信在 Perl 6 中有一些方法可以做到这一点。这确实是一种不同的语言。我不知道有任何其他语言可以像您想要的那样找到重叠的正则表达式匹配项。

它找到的第一个匹配项是开头的子字符串 VMV。然后它从停止的地方开始搜索,并找到匹配 ARGDLGVE。然后它会从它停止的地方再次尝试,这个阶段是在字符串的末尾。所以它在末尾找到空子串作为匹配项。 (请注意,您的正则表达式匹配空字符串。)禁止正则表达式再次查找相同的空字符串,因为这会导致无限循环,因此它会停止搜索。

这段代码有什么意义?因为我真的看不到这样做的好方法,我希望有其他方法可以实现您的目标。您可以遍历 $text 的所有可能子字符串,并使用 /^$regex$/ 单独检查每个子字符串。根据我的统计,您会找到 70 个匹配项(包括重复项和空字符串)。也许您想要更严格的正则表达式?

我重构了您的代码,以便更容易直观地了解正在发生的事情。

my $regex = qr/
  M?        # 1:  Match M,                  greedy, 0 or 1 times.
  [VI]?     # 2:  Match V or I,             greedy, 0 or 1 times.
  A?        # 3:  Match A,                  greedy, 0 or 1 times.
  R?        # 4:  Match R,                  greedy, 0 or 1 times.
  G?        # 5:  Match G,                  greedy, 0 or 1 times.
  D?        # 6:  Match D,                  greedy, 0 or 1 times.
  [LM]?     # 7:  Match L or M,             greedy, 0 or 1 times.
  G?        # 8:  Match G,                  greedy, 0 or 1 times.
  [IVMAL]?  # 9:  Match I, B, M, A or L,    greedy, 0 or 1 times.
  E?        # 10: Match E,                  greedy, 0 or 1 times.
/x;
my $text = "VMVARGDLGVE";

if ( my @matched = $text =~ /$regex/gx ) {
  print "No of matches: ", scalar(@matched), "\n";
  print "<<$_>>\n" foreach @matched;
}

您的正则表达式在不违反其 NFA 正则表达式引擎规则的情况下,以尽可能多的方式进行匹配。

  1. 其中一个规则是 "leftmost";离左边最近的子串 在允许总匹配成功的目标中将被选择。

  2. 另一个规则是贪心量词将匹配尽可能多的 可能,并且只会放弃他们拥有的子匹配,如果它变成 使完整匹配成功所必需的。放弃一部分 他们的比赛涉及回溯。避免回溯,除非 量词占用了太多,必须放弃它以允许 完全匹配成功。

  3. 还有一个规则就是迭代匹配从点开始 上一场比赛结束了。

遍历目标字符串,"V" 匹配子模式 [VI]?(以下称为子模式 #2)。贪婪的量词持有 'V',并且只有在以后被迫释放它时才会释放它,以实现更大的利益。

目标字符串中的

"M" 匹配子模式 #7。 "V" 匹配子模式#9。第一次迭代完成,匹配 "VMV"。

现在目标字符串的其余部分看起来像 "ARGDLGVE"。 pos 标记位于 3(目标字符串中的第 4 个字符),因此第二次迭代的匹配从那里开始。 'A' 匹配子模式#3,'R' 匹配#4,'G' 匹配#5,'D' 匹配#6,'L' 匹配#7 , 'G' 匹配#8,'V' 匹配#9,'E' 匹配#10。第二次迭代完成,匹配了目标字符串中的 'ARGDLGVE'。

在第三次迭代中,pos 标记位于 11,即目标字符串中最后一个字符之后。因此,将空字符串与您的正则表达式进行比较。因为正则表达式中的每个量词都是“0 或 1”,所以正则表达式匹配空字符串是可以接受的。所以第三次迭代完成,匹配了“”(空字符串)。

您获得了三个匹配项:"VMV"、"ARGDLGVE" 和“”。

您可能希望做的一件事是控制 pos 标记。将您的正则表达式放在一个 while 循环中,在循环终止之前,从字符串的开头再前进 pos 一个位置。但这只能解决您在上述第三条规则中遇到的问题。你仍然会遇到量词做非常具体的事情的问题,不要仅仅因为你认为它很方便就违反它们自己的规则。

关键是正则表达式引擎不是排列引擎。它的工作是确定给定的目标字符串是否与给定的模式匹配,遵循一组定义明确(尽管有时令人困惑)的规则。

我不确定您要解决的更大问题是什么。如果您只是想扩展一组范围,使用 CPAN 模块 String::Range::Expand 可能会更成功。可能还有其他 CPAN 模块也可以为您进行范围扩展,但这个可能是一个很好的起点。

这几乎是与 Count overlapping regex matches in Perl OR Ruby 相同的问题。

此代码与 perldoc perlre 几乎没有变化,位于标题为 "Special Backtracking Control Verbs":

的部分下
use strict;
use warnings;

my $regex = qr/M?[VI]?A?R?G?D?[LM]?G?[IVMAL]?E?/;
my $text  = 'VMVARGDLGVE';

my $count = 0;
$text =~ /$regex(?{print "$&\n"; $count++})(*FAIL)/g;
print "Got $count matches\n";

该脚本会计算空字符串匹配数,得出 97 个匹配数。