如何使用量词获取所有捕获组的位置?

时间:2017-02-01 14:16:41

标签: regex perl

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

一个例子:

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&#34;。

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

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)来获得具有量词的捕获组的所有可能匹配吗?

提前致谢!

2 个答案:

答案 0 :(得分:1)

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

<svg width="200px" height="200px">
  <g transform="translate(70,70)">
    <path d="M -40,-40 l 80,0 l 0,80 l -80,0 l 0,-80 z" style="fill: gray"></path>
    <g>
      <text text-anchor="middle" dominant-baseline="middle" style="fill: white" transform="scale(2)">
        <tspan>test</tspan>
      </text>
    </g>
  </g>

</svg>

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 :(得分:0)

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

在第一个例子中,我只能看到一个捕获组。量词允许它多次使用,但它仍然是一个捕获组。因此匹配子模式的每个新出现都会覆盖先前在那里捕获的值。即使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/;

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

希望这有帮助。