7

占有量词是贪婪的并且拒绝回溯。正则表达式/.{1,3}+b/应该意味着:匹配除换行符以外的任何字符,1 到 3 次,尽可能多且不回溯。然后匹配字符b

在这个例子中:

'ab'.sub /.{1,3}+b/, 'c'    #=> "c"

与事实相反,不应进行替代。

这两个示例中的结果不同:

'aab'.sub /.{0,1}+b/, 'c'   #=> "c"
'aab'.sub /.?+b/, 'c'       #=> "ac"

将此与 Scala 进行比较,他们给出了相同的答案:

scala> ".{0,1}+b".r.replaceAllIn("aab", "c")
res1: String = ac
scala> ".?+b".r.replaceAllIn("aab", "c")
res2: String = ac

这是一个 Ruby 错误,还是有可能激发这种行为??也许,Oniguruma 出于某种原因对所有量词,实现了所有格*+除了通用量词{m,n}?如果是这样,为什么?

4

2 回答 2

5

真正发生的事情

似乎+后跟范围量词并没有为范围量词提供所有格属性。相反,它被视为前面重复一次或多次的任何内容。以.{1,3}+b为例,它将等效于(?:.{1,3})+b.

解决方法

您可以使用更通用的构造非回溯组(或原子分组)来解决此问题(?>pattern)。让我们以一般情况pattern{n,m}+为例,构造具有非回溯组的等效正则表达式(等效于 Java 与 匹配时的行为pattern{n,m}+):

(?>(?>pattern){n,m})

为什么有 2 个级别的非回溯组?2 是必要的,因为:

  • 当为pattern(一个重复实例)找到匹配项时,pattern不允许回溯。(注意,只要没有找到实例,pattern就允许在其中回溯)。这与内部非回溯组进行了模拟。
  • 当找不到更多pattern的实例时,不允许回溯以删除任何实例。这与外部非回溯组进行了模拟。

我不确定这里是否有任何警告。如果您发现此方法未模拟任何情况,请对我进行评论。

测试

测试 1

起初,我测试了这个正则表达式:

(.{1,3}+)b

最初,我在没有捕获组的情况下进行了测试,但结果令人惊讶,我需要捕获组来确认发生了什么。

在这个输入上:

2343333ab

结果是整个字符串匹配,并且捕获的捕获组2343333a(没有b结尾)。这表明上限已经以某种方式被打破。

rubular 演示

测试 2

第二个测试揭示了范围量词{n}的行为如何不能被修改为所有格,并且很可能这也适用于其他范围量词{n,}{n,m}. 相反,以下+只会表现出重复 1 次或多次的行为。

(我最初的结论是+覆盖了上限,但事实证明是错误的)。

测试正则表达式:

(.{3}+)b

输入字符串:

23d4344333ab
234344333ab
23434433ab

在捕获组 1 中捕获的匹配都是 3 的倍数。从上到下,正则表达式分别跳过输入字符串的 2、1、0 个字符。

带注释的输入字符串([]表示匹配整个正则表达式,()表示捕获组 1 捕获的文本):

23[(d4344333a)b]
2[(34344333a)b]
[(23434433a)b]

rubular 演示

测试代码以解决问题

这是 Java 中的测试代码,表明外部和内部非回溯组都是必要的。ideone

class TestPossessive {
  public static void main(String args[]) {
    String inputText = "123456789012";
    System.out.println("Input string: " + inputText);
    System.out.println("Expected: " + inputText.replaceFirst("(?:\\d{3,4}(?![89])){2,}+", ">$0<"));
    System.out.println("Outer possessive group: " + inputText.replaceFirst("(?>(?:\\d{3,4}(?![89])){2,})", ">$0<"));
    System.out.println("Inner possessive group: " + inputText.replaceFirst("(?>\\d{3,4}(?![89])){2,}", ">$0<"));
    System.out.println("Both: " + inputText.replaceFirst("(?>(?>\\d{3,4}(?![89])){2,})", ">$0<"));

    System.out.println();

    inputText = "aab";
    System.out.println("Input string: " + inputText);
    System.out.println("Expected: " + inputText.replaceFirst(".{1,3}+b", ">$0<"));
    System.out.println("Outer possessive group: " + inputText.replaceFirst("(?>.{1,3})b", ">$0<"));
    System.out.println("Inner possessive group: " + inputText.replaceFirst("(?>.){1,3}b", ">$0<"));
    System.out.println("Both: " + inputText.replaceFirst("(?>(?>.){1,3})b", ">$0<"));
  }
}
于 2013-03-20T10:14:30.180 回答
2

这似乎是在 Oniguruma 的意图。文档{n,m}+, {n,}+, {n}+ are possessive op. in ONIG_SYNTAX_JAVA only。我猜这是因为向后兼容的原因,还是?

于 2013-03-20T11:48:51.950 回答