具有可变组数的正则表达式?

37

能否创建一个具有可变组数的正则表达式?

例如,运行以下代码之后...

Pattern p = Pattern.compile("ab([cd])*ef");
Matcher m = p.matcher("abcddcef");
m.matches();

我希望有这样的结果:

  • m.group(1) = "c"
  • m.group(2) = "d"
  • m.group(3) = "d"
  • m.group(4) = "c".

(背景:我正在解析一些数据行,其中一个“字段”是重复的。我希望避免对这些字段进行matcher.find循环。)


如@ Tim Pietzcker在评论中指出,perl6.NET支持此功能。

8个回答

27
根据文档,Java正则表达式无法实现以下功能:

与组相关的捕获输入始终是该组最近匹配的子序列。如果由于量化而对组进行第二次评估,则如果第二次评估失败,则将保留其先前捕获的值(如果有)。例如,将字符串“aba”与表达式(a(b)?)+匹配,这会导致第二个组设置为“b”。在每次匹配开始时,所有捕获的输入都将被丢弃。

(已加重显示)


5

4

我没有使用过Java正则表达式,但对于许多语言来说,答案是:不行。

捕获组似乎是在解析正则表达式时创建的,并在匹配字符串时填充。表达式(a)|(b)(c)有三个捕获组,只有其中一个或两个可以被填充。(a)*只有一个组,解析器在匹配后将最后一次匹配留在该组中。


1
.NET具有捕获功能,因此您可以访问重复子组的单个匹配。 - Tim Pietzcker
1
@Tim,啊,看那个。那正是我想要的(但用Java)。 - aioobe

2
Pattern p = Pattern.compile("ab(?:(c)|(d))*ef");
Matcher m = p.matcher("abcdef");
m.matches();

应该做你想要的事情。

编辑:

@aioobe,我现在明白了。你想要做类似语法的东西。

A    ::== <Foo> <Bars> <Baz>
Foo  ::== "foo"
Baz  ::== "baz"
Bars ::== <Bar> <Bars>
        | ε
Bar  ::== "A"
        | "B"

把所有的Bar匹配项都提取出来。

不,使用java.util.regex是没有办法做到的。你可以在Bars的匹配项上递归并使用正则表达式,或者使用像ANTLR这样的解析器生成器,并将副作用附加到Bar上。


嗯,那不是可变数量的组。那总是两个组。也许我简化了我的例子太多了。(澄清问题。) - aioobe
@aioobe,我编辑了这篇文章以回答您澄清的问题。 - Mike Samuel

0

我刚遇到了非常类似的问题,并通过 while 循环和重置匹配器的组合来实现了“可变数量的组”。

    int i=0;
    String m1=null, m2=null;

    while(matcher.find(i) && (m1=matcher.group(1))!=null && (m2=matcher.group(2))!=null)
    {
        // do work on two found groups
        i=matcher.end();
    }

但这是针对我的问题(有两个重复的

)的。
    Pattern pattern = Pattern.compile("(?<=^ab[cd]{0,100})[cd](?=[cd]{0,100}ef$)");
    Matcher matcher = pattern.matcher("abcddcef")
    int i=0;
    String res=null;

    while(matcher.find(i) && (res=matcher.group())!=null)
    {
        System.out.println(res);
        i=matcher.end();
    }

由于预测长度的先行断言和后发断言,您将失去使用*+来指定任意重复长度的能力。


0
如果你会遇到合理的最大匹配组数:
"ab([cd])?([cd])?([cd])?([cd])?([cd])?([cd])?([cd])?([cd])?ef"

这个例子适用于0-8个匹配。我承认这很丑陋,不易读懂。


2
"ab" + "([cd])?".repeat(8) + "ef" 稍微好看一点。 - aioobe

0
我认为回溯会抑制这种行为,并且对类似圣经的东西在其分组累积状态下的/([\S\s])/效果。即使可以完成,输出也是不可知的,因为组将失去位置意义。最好在全局范围内对相似的内容进行单独的正则表达式匹配,并将其存储到数组中。

0
我想避免使用matcher.find循环来处理这些字段。
正如其他答案所述,这是无法避免的。为了完整起见,以下是使用第二个Pattern来遍历单个匹配项的方法。请注意,星号的位置在圆括号内而不是之后。
Pattern subPattern = Pattern.compile("[cd]");
Pattern pattern = Pattern.compile("ab(" + subPattern.pattern() + "*)ef"); // DRY, but probably safer ways to do it for the case that subPattern needs to be changed.
Matcher matcher = pattern.matcher("abccdcddef is great and all, but have you heard about abef and abddcef?");
List<String> letterSequence = new ArrayList<>();
while (matcher.find()) {
    String letters = matcher.group(1);
    Matcher subMatcher = subPattern.matcher(letters);
    while (subMatcher.find()) {
        String letter = subMatcher.group();
        letterSequence.add(letter);
    }
}
System.out.println(letterSequence);

输出:

[c, c, d, c, d, d, d, d, c]


这看起来有点错误。如果 subPatternabc,那么 * 只会应用于 c。你可能想要添加一对括号。 - aioobe
关于程序员更改subPattern应该检查什么,这是一个有争议的论点。我在这一行添加了注释。不确定是否有一个简单的解决方案。作为它现在的状态,额外的抽象来解耦两个模式似乎有些啰嗦。 - Zyl

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