【问题标题】:Find multiple regex matches in Java prohibiting non-matches在Java中查找多个正则表达式匹配,禁止不匹配
【发布时间】:2020-05-06 02:00:04
【问题描述】:

我有一个 Java Pattern 例如\s+(foo|bar) 来查找空格后foobar 的所有匹配项。使用匹配组,我可以提取实际匹配的文本。

Pattern pattern=Pattern.compile("\\s+(foo|bar)");
Matcher matcher = pattern.match(someText);
while(matcher.find()) {
  String value = matcher.group(1);
  ...
}

这适用于像foo foo bar 这样的字符串(注意前面的空格),但它也可以匹配像foo foo bad 这样的字符串。我怎样才能阻止匹配器匹配不匹配的后续字符运行,或者检测到字符被跳过或没有更多字符剩余?换句话说,我希望被匹配的整个字符串是与模式匹配的一系列后续字符串。我怎么能保证呢?

这里的重点是继续通过字符串查找匹配。我可以轻松拆分字符串,然后执行额外的比较,但我不想要多个正则表达式传递、数组/列表创建等的开销。

【问题讨论】:

  • bad foo foo bar 呢?
  • @Nick,我也不想匹配那个,我想知道有些字符不匹配。
  • 您能否包括一些示例输入以及确切的输出应该是什么。我不清楚。

标签: java regex regex-group


【解决方案1】:

在正则表达式前面加上\GPattern 的 Javadoc 说:

\G - 上一场比赛结束

当然,在第一场比赛中,“上一场比赛的结束”就是输入的开始。

这确保了正则表达式匹配都是连续的,从输入的开头开始。并不意味着正则表达式会到达输入的末尾,您必须自己检查。

例子

public static void main(String[] args) {
    test("abc");
    test(" foo foo bar");
    test(" foo foo bad");
    test(" foo bad foo");
}
static void test(String input) {
    System.out.println("'" + input + "'");
    int lastEnd = 0;
    Matcher m = Pattern.compile("\\G\\s+(foo|bar)").matcher(input);
    while (m.find()) {
        System.out.printf("  g0='%s' (%d-%d), g1='%s' (%d-%d)%n",
                          m.group(), m.start(), m.end(),
                          m.group(1), m.start(1), m.end(1));
        lastEnd = m.end();
    }
    if (lastEnd == input.length())
        System.out.println("  OK");
    else
        System.out.println("  Incomplete: Last match ended at " + lastEnd);
}

输出

'abc'
  Incomplete: Last match ended at 0
' foo foo bar'
  g0=' foo' (0-4), g1='foo' (1-4)
  g0=' foo' (4-8), g1='foo' (5-8)
  g0=' bar' (8-12), g1='bar' (9-12)
  OK
' foo foo bad'
  g0=' foo' (0-4), g1='foo' (1-4)
  g0=' foo' (4-8), g1='foo' (5-8)
  Incomplete: Last match ended at 8
' foo bad foo'
  g0=' foo' (0-4), g1='foo' (1-4)
  Incomplete: Last match ended at 4

为了比较,在正则表达式中没有\G,该代码的输出将是:

'abc'
  Incomplete: Last match ended at 0
' foo foo bar'
  g0=' foo' (0-4), g1='foo' (1-4)
  g0=' foo' (4-8), g1='foo' (5-8)
  g0=' bar' (8-12), g1='bar' (9-12)
  OK
' foo foo bad'
  g0=' foo' (0-4), g1='foo' (1-4)
  g0=' foo' (4-8), g1='foo' (5-8)
  Incomplete: Last match ended at 8
' foo bad foo'
  g0=' foo' (0-4), g1='foo' (1-4)
  g0=' foo' (8-12), g1='foo' (9-12)
  OK

如您所见,最后一个示例无法检测到文本 bad 被跳过。

【讨论】:

  • 安德烈亚斯,这太棒了!这正是我一直在寻找的东西;太感谢了。一个小警告:\G 阻止将模式嵌入到更大的正则表达式中(例如"test ((?:" + pattern + ")+)?"),因此如果两者都需要,则需要将模式提取为两个:用于嵌入的普通模式,另一个前缀为\G 用于迭代匹配。
  • @GarretWilson \G 仅在您进行多次匹配时使用,即进行多次find() 调用,因为定义中的“先前匹配”特指先前调用find() (或扩展逻辑,如 replaceAll(...)results(),在内部使用等效的 find() 循环)。 --- 你的重复模式(?:" + pattern + ")+ 不需要\G,因为它不允许任何重复之间的任何东西,并且整个正则表达式是“一个匹配”。
  • @GarretWilson 因此,如果pattern 用于直接使用或用于嵌入,那么\G 不属于pattern,它是pattern 的用户决定是否其他允许模式匹配之前/之间的字符。例如。对于不允许compile("\\G(?:" + pattern + ")")compile("(?:" + pattern + ")+") 是等价的,对于allowedcompile(pattern)compile("(?:(?s:.*?)(?:" + pattern + "))+") 是等价的。 (需要额外的(?:x) 来防范pattern 使用顶级|
  • 是的,我明白这一切,这一切都很好。我只是指出这个警告,以防有人试图将此表单嵌入另一个正则表达式。在我的库中,我轻松地为这两个独立的用例创建了两个独立的正则表达式模式。
【解决方案2】:

需要做一个额外的match 的解决方案是首先尝试将输入与以下正则表达式匹配:

^(\s+(foo|bar))+$

然后你可以做你的重复发现:

import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class Test
{
    public static void main(String[] args) {
        String[] tests =  {
            " foo foo bar",
            " foo foo x foo bar"
        };
        Pattern pattern1 = Pattern.compile("(\\s+(foo|bar))+");
        Pattern pattern2 = Pattern.compile("\\s+(foo|bar)");
        for (int i = 0; i < tests.length; i++) {
            String test = tests[i];
            Matcher m1 = pattern1.matcher(test);
            if (m1.matches()) {
                System.out.println("Matches against: '" + test + "'");
                Matcher m2 = pattern2.matcher(test);
                while (m2.find()) {
                    System.out.println("\t'" + m2.group() + "'");
                }
            }
        }
    }
}

打印:

Matches against: ' foo foo bar'
        ' foo'
        ' foo'
        ' bar'

如果整个输入不必匹配,那么我们使用正则表达式来查找匹配的字符串的前缀:

^(\s+(foo|bar))+

您可以根据输入测试此匹配的长度,以确定整个字符串是否匹配。

然后:

import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class Test
{
    public static void main(String[] args) {
        String[] tests =  {
            " foo foo bar",
            " foo foo x foo bar"
        };
        Pattern pattern1 = Pattern.compile("^(\\s+(foo|bar))+");
        Pattern pattern2 = Pattern.compile("\\s+(foo|bar)");
        for (int i = 0; i < tests.length; i++) {
            String test = tests[i];
            Matcher m1 = pattern1.matcher(test);
            if (m1.find()) {
                String s = m1.group();
                System.out.println("Matches against: '" + s + "'");
                Matcher m2 = pattern2.matcher(s);
                while (m2.find()) {
                    System.out.println("\t'" + m2.group() + "'");
                }
            }
        }
    }
}

打印:

Matches against: ' foo foo bar'
        ' foo'
        ' foo'
        ' bar'
Matches against: ' foo foo'
        ' foo'
        ' foo'

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多