Raku正则表达式:如何知道在交替中捕获了哪个组

10

使用Perl(以及几乎所有正则表达式引擎),每个组都按顺序编号。

例如,对于以下代码:

'bar' =~ m/(foo)|(bar)/;

print $1 // 'x'; # (1-based index)
print $2 // 'x'; # (1-based index)

打印输出xbar

但是,对于Raku而言,它的行为就像整个正则表达式都被包装在一个分支重置组中:

'bar' ~~ m/(foo)|(bar)/;

print $0 // 'x'; # (0-based index)
print $1 // 'x'; # (0-based index)

打印barx

我对这个行为感到满意 :) ,但有时候知道在备选项下捕获的是哪个组是很有用的。

我如何在raku中知道该组?


OP 可能已经知道这一点,但 Perl5 和 Raku 之间的另一个区别是 Raku 的 | 替代运算符执行最长令牌匹配(LTM),而不是顺序(即“首个命名”)令牌匹配。请参见:https://docs.raku.org/language/regexes#Longest_alternation:_| and https://docs.raku.org/language/5to6-nutshell.html#Longest_token_matching_(LTM)_displaces_alternation。 - jubilatious1
2个回答

10

有几种方法可供选择,效用不同。

其中一种方法是明确告诉Raku你想要的数字:

'bar' ~~ m/$1=(foo)|$2=(bar)/;

如果你扩展正则表达式,计数将从$3继续。

一种不太推荐的方法是悄悄加入一组额外的括号:

'bar' ~~ m/(foo)|()(bar)/;

foo将匹配$0中的第一个,$1将未定义,并且bar将匹配$1,$0为空(但不是未定义)。TIMTOWTDI,但这不是一个好方法;-)

另一种方法是使用标志:

 my $flag;
'bar' ~~ m/(foo {$flag = 'first'} ) | (bar {$flag = 'second'} )/;

根据匹配情况将设置标志。这实际上是一种不错的方法,特别是如果您的标志是二进制的,并且您将在其上运行一些逻辑。

另一种类似的方法是利用通常用于操作类但仍可以内联使用的.make/.made

'bar' ~~ m/(foo {make 'first'} ) | (bar {make 'second'} )/;
say $0.made; # 'second'

如果你有很多元数据想要关联它,这个选择是不错的(但仅仅想知道选了哪一个可能过于复杂)。


哇,比预期的方法还多。当然,TIMTOWTDI。谢谢! - Julio

2

有一些因素会导致捕获索引重置,其中之一是使用 |||

将其放置在另一个捕获组中也会导致重置。(因为匹配结果是一棵树。)


在设计Raku时,一切都被重新设计以更加一致、有用和强大,包括正则表达式。

如果你有一个像这样的选择:

/  (foo)  |  (bar)  /

您可能想这样使用它:
$line ~~ /  (foo)  |  (bar)  /;
say %h{ ~$0 };

如果 (bar)$1 的话,你需要这样写才行:
$line ~~ /  (foo)  |  (bar)  /;
say %h{ ~$0 || ~$1 };

通常情况下,捕获组编号从零开始更有用。

这样做也使正则表达式更像通用编程语言。(每个“块”都是一个独立的子表达式。)


有时重新编号捕获组可能很不错。

/ ^
[   (..) '-'  (..) '-' (....)  # mm-dd-yyyy
|   (..) '-' (....)            # mm-yyyy
]
$ /

注意,yyyy 部分是根据 dd 部分是否包含而分别为 $2$1

my $day   = +$2 ?? $1 !! 1;
my $month = +$0;
my $year  = +$2 || +$1;

我们可以将yyyy重新编号为始终为$2
/ ^
[   (..) '-'  (..) '-' (....)  # mm-dd-yyyy
|   (..) '-' $2 = (....)       # mm-yyyy
]
$ /

my $day   = +$1 || 1;
my $month = +$0;
my $year  = +$2;

如果我们还需要接受 yyyy-mm-dd,该怎么办呢?

/ ^
[   (..) '-' (..) '-' (....)                # mm-dd-yyyy
|   (..) '-' $2 = (....)                    # mm-yyyy
|   $2 = (....) '-' $0 = (..) '-' $1 = (..) # yyyy-mm-dd
]
$ /

my $day   = +$1 || 1
my $month = +$0;
my $year  = +$2;

实际上,现在我们有很多捕获组,让我们再看一下如果|不会导致编号的捕获组从$0重新开始,我们将如何处理它。

/ ^
[   (..) '-' (..) '-' (....) # mm-dd-yyyy
|   (..) '-' (....)          # mm-yyyy
|   (....) '-' (..) '-' (..) # yyyy-mm-dd
]
$ /

my $day   = +$1 || +$7 ||   1;
my $month = +$0 || +$3 || +$6;
my $year  = +$2 || +$4 || +$5;

这不太好。

首先,您需要确保正则表达式和my $day正确匹配。

快速计算捕获组数量,确保这些数字与正确的捕获组匹配。


当然,仍然存在一个问题,即具有名称的概念实际上被数字捕获。

因此,我们应该使用名称代替。

/ ^
[   $<month> = (..) '-' $<day> = (..) '-' $<year> = (....) # mm-dd-yyyy
|   $<month> = (..) '-' $<year> = (....)                   # mm-yyyy
|   $<year> = (....) '-' $<month> = (..) '-' $<day> = (..) # yyyy-mm-dd
]
$ /

my $day   = +$<day> || 1;
my $month = +$<month>;
my $year  = +$<year>;

简单来说,我会这样做:

/ $<foo> = (foo)  |  $<bar> = (bar) /;


if $<foo> {
    …
} elsif $<bar> {
    …
}

3
好的回答。我曾考虑过包含原因,但很高兴我没有这样做,因为您更好地解释了它。绝对同意使用命名捕获。我认为我在生产咖啡中只用过数值捕获一两次,否则始终使用命名捕获。 - user0721090601

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接