为啥我使用这些 Raku 正则表达式会得到不同的回溯?

Posted

技术标签:

【中文标题】为啥我使用这些 Raku 正则表达式会得到不同的回溯?【英文标题】:Why do I get different backtracking with these Raku regexes?为什么我使用这些 Raku 正则表达式会得到不同的回溯? 【发布时间】:2020-12-09 23:13:56 【问题描述】:

我意外地回溯了 Raku 正则表达式的 + 量词。

在这个正则表达式中:

'abc' ~~ m/(\w+) say $0  <? $0.substr(*-1) eq 'b' >/;

say $0;

我得到了预期的结果:

「abc」  # inner say
「ab」   # inner say

「ab」   # final say

也就是说,(贪婪的)+ 量词获取所有字母,然后条件失败。之后,它通过释放最后一个得到的字母开始回溯,直到条件评估为真。

但是,当我将量词放在捕获组之外时,回溯的工作方式似乎不同:

'abc' ~~ m/[(\w)]+ say $0  <? $0.tail eq 'b' >/;

say $0;

结果:

[「a」 「b」 「c」]  # inner say
[「a」 「b」 「c」]  # why this extra inner say? Shouldn't this backtrack to [「a」 「b」]?
[「a」 「b」 「c」]  # why this extra inner say? Shouldn't this backtrack to [「a」 「b」]?
[「b」 「c」]      # Since we could not successfully backtrack, We go on matching by increasing the position
[「b」 「c」]      # Previous conditional fails. We get this extra inner say
[「c」]          # Since we could not successfully backtrack, We go on matching by increasing the position

Nil            # final say, no match because we could not find a final 'b'

这是预期的行为吗?如果是这样:为什么它们的工作方式不同?是否可以模仿第一个正则表达式但仍将量词保留在捕获组之外?

注意:

使用惰性量词“解决”问题...这是意料之中的,因为差异似乎发生在回溯中,而惰性量词不会发生这种情况。

'abc' ~~ m/[(\w)]+? say $0  <? $0.tail eq 'b' >/;

[「a」]
[「a」 「b」]

[「a」 「b」]

但是出于性能原因,我宁愿使用贪婪的量词(这个问题中的例子是一个简化)。

【问题讨论】:

在你的第二个例子中,我不明白方括号的使用。如果我将方括号放在括号内,我会得到与单独使用括号相同的结果,即 ([\w+])([\w])+ 模仿它们的非方括号包含对应项。 @jubilatious1 是的,括号没用:)。原始的正则表达式更复杂,我开始剥离它的一部分以获得最简单的情况。我终于忘记去掉括号了。 【参考方案1】:

我认为问题不在于回溯。但是看起来中间的$0 暴露了保留了之前的迭代捕获。考虑这个表达式,

'abc' ~~ m/[(\w)]+ say "Match:",$/.Str,";\tCapture:",$0  <? False >/;

这是输出:

Match:abc;  Capture:[「a」 「b」 「c」]
Match:ab;   Capture:[「a」 「b」 「c」]
Match:a;    Capture:[「a」 「b」 「c」]
Match:bc;   Capture:[「b」 「c」]
Match:b;    Capture:[「b」 「c」]
Match:c;    Capture:[「c」]

如您所见,匹配顺序正确,abc ab a ...。但是ab匹配的捕获数组也是[「a」 「b」 「c」]。我怀疑这是一个错误。


对于您的情况,有几种方法。

    只需使用$/ 进行条件检查
    'abc' ~~ m/[(\w)]+  <? $/.Str.substr(*-1) eq 'b' >/;
    
    或者,另外还使用限定词捕获组。
    'abc' ~~ m/([(\w)]+) <? $0[0][*-1] eq 'b' >/;
    
    这里$0匹配外部组,$0[0]匹配第一个内部组,$[0][*-1]匹配本次迭代中最终匹配的字符。

【讨论】:

$/.Str.substr(*-1) eq 'b' 更容易的是$/.ends-with: 'b' 嗨!您用额外的捕获组包装量化器的解决方法就像一个魅力!让我们看看 rakudo 的人怎么说这是一个错误 我已经提交了一个问题With (foo)+ the corresponding sub-captures aren't removed during backtracking。 @raiph,谢谢 @jubilatious1,给出的正则表达式,是用于演示问题的简化表达式。根据演示输出,希望您同意存在差异。是的,只有当我们使用回溯和$0 时才会出现问题。

以上是关于为啥我使用这些 Raku 正则表达式会得到不同的回溯?的主要内容,如果未能解决你的问题,请参考以下文章

为啥不通知更新?

正则表达式:为啥这些行中的文字“鲍勃”不匹配?

notepad++匹配汉字的正则表达式与Java中的为啥不同?

为啥两次运行完全相同的正则表达式的结果不同? [复制]

为啥 Java 正则表达式在 Linux 和 Windows 上的行为不同?

为啥 C# 正则表达式信用卡验证函数会使用无效值进行验证? [复制]