【问题标题】:How to get the position of all capture groups with quantifiers?如何使用量词获取所有捕获组的位置?
【发布时间】:2017-02-01 14:16:41
【问题描述】:

我有一个小问题。我有一个带有多个捕获组的 。其中一些有量词(如'+')。如果未添加量词,则@-@+ 数组将很好地填充捕获组的匹配位置,但如果添加量词,则仅检测到最后一个匹配项。但我想拥有所有这些。

一个例子:

my $s = 'xx1a2b3cyy';
my $re = qr/^xx(\d\w)+/;

所以我想知道匹配是 '1a', '2b', '3c' 在 2、4、6。

简单匹配给出:

if ($s =~ $re) {
  print "Match @-, @+\n";
  for (my $i = 0; $i < @-; ++$i) {
    print 'i: ', $i, " - '", substr($s, $-[$i], $+[$i] - $-[$i]), "\n";
  }
}

给予:

Match 0 6, 8 8
i: 0 - 'xx1a2b3c
i: 1 - '3c

所以只有最后一次捕获组匹配被记住。

我的下一个简单尝试是这并不是我真正想要的,因为 RE 不同:

$re = qr/(\d\w)/;
my @s = ($s =~ /$re/g);
print "RE: '@s'\n";
while ($s =~ /$re/g) {
  print "Match @-, @+\n";
  for (my $i = 0; $i < @-; ++$i) {
    print 'i: ', $i, " - '", substr($s, $-[$i], $+[$i] - $-[$i]), "\n";
  }
}

给予:

RE: '1a 2b 3c'
Match 2 2, 4 4
i: 0 - '1a
i: 1 - '1a
Match 4 4, 6 6
i: 0 - '2b
i: 1 - '2b
Match 6 6, 8 8
i: 0 - '3c
i: 1 - '3c

但这不是我想要的,因为它会匹配像'ZZ1aAA2bBB3cZZ' 这样的字符串。

所以我必须以某种方式将两者结合起来。我能得到的最好的:

$re = '^xx(?:\d\w)*?\G(\d\w)';
pos($s) = 2;
while ($s =~ m($re)g) {
  print "Match pos: ", pos($s), ', G: ', $1, ", '@-', '@+'\n"
}

给予:

Match pos: 4, G: 1a, '0 2', '4 4'
Match pos: 6, G: 2b, '0 4', '6 6'
Match pos: 8, G: 3c, '0 6', '8 8'

这几乎很好,但为此我需要知道第一个可能匹配的位置。如果设置不正确,它将不匹配任何内容。只有去掉非贪心部分才能确定第一个位置:

$re = '^xx(\d\w)';
if ($s =~ m($re)) {
  print "Match: '@-', '@+'\n";
}

给出:

Match: '0 2', '4 4'

所以$-[1] 给出了第一个位置,但为此我必须“手动”修改 RE。

如果我将代码执行添加到模式中,我几乎可以得到我需要的东西:

use re 'eval';
$re = '^xx(\d\w)+(??{print "Code: <@-> <@+>\n"})';
$s =~ m($re) and print "Match\n";

给予:

Code: <0 6> <8 8>
Code: <0 4> <6 6>
Code: <0 2> <4 4>

为此,我需要添加(?{ code }) 部分。

有没有人知道更简单的方法(我的意思是不需要修改原始 RE)来获取具有量词的捕获组的所有可能匹配项?

提前致谢!

【问题讨论】:

  • my @matches = ( 'xx1a2b3cyy' =~ m/^xx(\d\w)+/ ); 仅包含 3c。使用use re 'debug' 告诉我们它得到1a2b3c 每个作为子模式的匹配项,但我不知道它们最终会在哪里。看起来第一个子模式的最后一个匹配只保存为 $1,这就解释了为什么 @+@- 只指向它。
  • @simbabque 是的,这与小码给出的“简单匹配给出:”相同。无论如何,如果没有 'g' 修饰符,它不应该返回更多值。
  • 但是如果你在我的例子中添加/g,你也只能得到最后一个。
  • @simbabque:是的,你可以获得最后一场比赛,而不是全部。它对我没有帮助,因为我想拥有一切。似乎只有(?{ code }) 可能会有所帮助...我必须检查它如何与带有量词的多个捕获组一起使用。

标签: perl regexp regex perl


【解决方案1】:

没有通用的解决方案;正则表达式引擎根本不存储必要的信息。您要求使用正则表达式作为解析器,这是不行的。


sub extract {
   for ($_[0]) {
      /^ xx /xg
         or return ();

      my @matches;
      push @matches, $1 while /\G (\d\w) /xg;
      return @matches;
   }
}

sub extract {
   my ($pairs) = $_[0] =~ /^xx((?:\d\w)+)/
      or return ();

   return unpack('(a2)*', $pairs);
}

如果你只是想要职位,那是一样的。

sub extract {
   for ($_[0]) {
      /^ xx /xg
         or return ();

      my @matches;
      push @matches, $-[1] while /\G (\d\w) /xg;
      return @matches;
   }
}

sub extract {
   $_[0] =~ /^xx((?:\d\w)+)/
      or return ();

   return map { $-[1] + ( $_ - 1 )*2 } 1..length($1)/2;
}

即使是非通用解决方案也很难使用正则表达式。假设您有以下模式:

xx(\d\w)+yy(\d\w)+zz

正确的解决方案是:

use Storable qw( dclone );

my $s = "xx1a2byy3c4dZZ...xx5a6byy7c8dzz";

local our $rv;
if (
   $s =~ /
      (?{ [] })
      xx
      (?: (\d\w) (?{ my $r = dclone($^R); push @{ $r->[0] }, $^N; $r }) )+
      yy
      (?: (\d\w) (?{ my $r = dclone($^R); push @{ $r->[1] }, $^N; $r }) )+
      zz
      (?{ $rv = $^R; })
   /x
) {
   say "\$1: @{ $rv->[0] }";
   say "\$2: @{ $rv->[1] }";
}

输出:

$1: 5a 6b
$2: 7c 8d

类似的东西

(zz(\d\w)+)+

需要

use Storable qw( dclone );

my $s = "zz1a2bzz3c4d";

local our $rv;
if (
   $s =~ /
      (?{ [] })
      (?:
         (?{ my $r = dclone($^R); push @$r, []; $r })
         zz
         (?: (\d\w) (?{ my $r = dclone($^R); push @{ $r->[-1] }, $^N; $r }) )+
      )+
      (?{ $rv = $^R; })
   /x
) {
   say "\$1: @$_" for @$rv;
}

输出:

$1: 1a 2b
$1: 3c 4d

【讨论】:

  • 感谢您的努力!不幸的是,它们适合示例中的当前模式,而不是真正独立于 RE 的解决方案。例如,我现在必须让捕获组中的表达式为 2 个字符宽。
  • 没有通用的解决方案。您要求使用正则表达式作为解析器,这是不行的。
  • 您能否详细说明“您可以在某种程度上使用 %-”?在我的尝试中,我最多只能找到与结果同名的实际捕获组的数量,它们上的量词不会增加数量。这与位置捕获的问题相同。如果您能向我展示您如何更好地利用%-,那真的会对我有所帮助。
  • 没关系,%- 不像我想象的那样工作。从我的答案中删除了这一点,并添加了另一个。
  • 感谢 $^R / $^N 示例,之前认为它们只能在嵌入代码中大量使用 local
【解决方案2】:

我想我可以对你看到的行为给出一些解释:

在第一个示例中,我只能看到一个捕获组。量词允许它被多次使用,但它仍然是一个捕获组。因此,匹配子模式的每一次新出现都会覆盖之前在那里捕获的值。即使 RE 引擎已经落后于它,但会发生回溯(例如,具有分支等的更高级模式),现在再次访问的捕获组可能会发生变化。由于@-@+ 占据了捕获组的位置(而不是发生子模式匹配),这可以解释为什么只包含最后一次出现的子模式。

您甚至可以使用命名子模式和%+/%- 并会遇到同样的事情。已经使用的(?{ }) 会变得更加明显,至少出于调试目的。但是use re 'debug' 可以匹配较短的正则表达式/字符串。

因此,请注意在匹配仍在进行时回溯以捕获组的影响!

但如果您不必关心回溯,我可以想出一种方法来处理带有量词的捕获组:

如果你的捕获组是(bla),你的量词是{0,3},把它转换成

(?:(bla)(?{ print $-[$#-],$+[$#-]."\n" })){0,3}.

您实际上将子模式放入另一个(非捕获)组。如果 RE 引擎完成了它,则执行有关到目前为止匹配的最后一个捕获组的代码。然后,周围组之外的量词负责代码片段的正确执行次数。

所以你的例子变成了这样:

use Data::Dumper;
my $s = 'xx1a2b3cyy';
my @submatches;
sub getem { push @submatches, [$-[$#-],$+[$#-]]; }
$s =~ m/^xx(?:(\d\w)(?{ getem() }))+/;
print Dumper(\@submatches);

这也适用于以这种方式转换的多个捕获组:

my $s = 'xx1a2b3cyy4de5fg6hihhh2';
$s =~ m/^xx(?:(\d\w)(?{ getem() }))+yy(?:(\d\w{2})(?{ getem() }))+hh/;

如果您的捕获组包含更多捕获组,您必须调整使用的索引。这就是为什么我更喜欢名称捕获组。

希望这会有所帮助。

【讨论】:

  • 您仍然需要检查整体匹配 - 如果 RE 引擎到达,嵌入式代码会立即执行。在+ 之后放置z 会导致失败,但@submatches 仍然包含所有触发的子匹配
  • 可能会使用(*FAIL) 而不是z,因此回溯引擎将尝试所有匹配项。
  • 我的观点似乎没有得到体现:如果整个匹配失败,那么捕获的位置可能不是您想要的。还是我错过了什么?
  • 那仍然使用问题特别要求删除的(?{ })。如果你打算使用(?{ }),那你就错了——回溯不会删除你存储的匹配项。你必须使用$^R(你应该使用$^N而不是那个复杂的表达式)。
  • 回溯并没有删除存储的子模式匹配正是我在这里反复尝试说的,但感谢您重复它,也许重新措辞让我们理解!并且(?{ })的使用并没有被明确要求删除——OP自己尝试解决他的问题——所以我又试了一次。如果你愿意,你可以在额外的部分展示你的 $^R / $^N 方法——我总是愿意学习。
猜你喜欢
  • 2013-02-10
  • 2014-06-21
  • 1970-01-01
  • 2011-09-16
  • 1970-01-01
  • 1970-01-01
  • 2019-07-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多