所有重叠的子字符串都匹配java正则表达式

时间:2012-07-03 01:22:20

标签: java regex matcher

是否有API方法返回与正则表达式匹配的所有(可能重叠)子串?

例如,我有一个文本字符串:String t = 04/31 412-555-1235;,我有一个模式:Pattern p = new Pattern("\\d\\d+");匹配两个或多个字符的字符串。

我得到的比赛是:04,31,412,555,1235。

如何获得重叠匹配?

我希望代码返回:04,31,41,412,12,55,555,55,12,123,1235,23,235,35。

理论上它应该是可能的 - 有一个明显的O(n^2)算法枚举并检查所有子串与模式。

修改

region(int start, int end)中使用Matcher方法更安全,而不是枚举所有子字符串。根据单独的提取子字符串检查模式可能会更改匹配的结果(例如,如果在模式的开头/结尾处存在非捕获组或字边界检查)。

编辑2

实际上,不清楚region()是否符合您对零宽度匹配的期望。规范含糊不清,实验结果令人失望。

例如:

String line = "xx90xx";
String pat = "\\b90\\b";
System.out.println(Pattern.compile(pat).matcher(line).find()); // prints false
for (int i = 0; i < line.length(); ++i) {
  for (int j = i + 1; j <= line.length(); ++j) {
    Matcher m = Pattern.compile(pat).matcher(line).region(i, j);
    if (m.find() && m.group().size == (j - i)) {
      System.out.println(m.group() + " (" + i + ", " + j + ")"); // prints 90 (2, 4)
    }
  }
}

我不确定最优雅的解决方案是什么。一种方法是在检查line是否匹配之前,采用pat的子字符串并使用适当的边界字符填充。

编辑3

这是我提出的完整解决方案。它可以处理原始正则表达式中的零宽度模式,边界等。它查看文本字符串的所有子字符串,并通过在开头和结尾用适当数量的通配符填充模式来检查正则表达式是否仅在特定位置匹配。它似乎适用于我尝试的案例 - 虽然我没有做过广泛的测试。它肯定效率低于它可能的效率。

  public static void allMatches(String text, String regex)
  {
    for (int i = 0; i < text.length(); ++i) {
      for (int j = i + 1; j <= text.length(); ++j) {
        String positionSpecificPattern = "((?<=^.{"+i+"})("+regex+")(?=.{"+(text.length() - j)+"}$))";
        Matcher m = Pattern.compile(positionSpecificPattern).matcher(text);

        if (m.find()) 
        {   
          System.out.println("Match found: \"" + (m.group()) + "\" at position [" + i + ", " + j + ")");
        }   
      }   
    }   
  }

编辑4

以下是更好的方法:https://stackoverflow.com/a/11372670/244526

编辑5

JRegex库支持查找与java正则表达式匹配的所有重叠子字符串(尽管它似乎暂时没有更新)。具体而言,documentation on non-breaking search指定:

  

使用非破坏性搜索,您可以找到a的所有可能发生的事件   模式,包括相交或嵌套的模式。这是   通过使用Matcher的方法proceed()而不是find()

来实现

3 个答案:

答案 0 :(得分:1)

我遇到了类似的情况,我尝试了上述答案,但在我的情况下,通过设置匹配器的开始和结束索引花了太多时间 但我想我找到了一个更好的解决方案,我在这里发布给其他人。 以下是我的代码片段。

if (textToParse != null) {
Matcher matcher = PLACEHOLDER_PATTERN.matcher(textToParse);
    while(matcher.hitEnd()!=true){
        Boolean result = matcher.find();
        int count = matcher.groupCount();
        System.out.println("Result " +result+" count "+count);
        if(result==true && count==1){
            mergeFieldName = matcher.group(1);
            mergeFieldNames.add(mergeFieldName);
           }
       }
  }

我使用了matcher.hitEnd()方法来检查我是否已经到达文本的末尾。

希望这会有所帮助。 谢谢!

答案 1 :(得分:0)

你能得到的最接近的是这样的。

"(?=((\\d*)\\d))(?=(\\d)\\d*)"

结果将是捕获第1,2和3组。

就我的想象而言,我只能想到捕捉零长度断言作为重新夺回弦乐相同位置的可行方法。在零长度断言之外捕获文本将一劳永逸地消耗文本(后视只能捕获Java中的固定长度,因此可以认为它是不可访问的。)

这个解决方案并不完美:除了重复(相同位置的文本!)和空字符串匹配之外,它不会捕获所有可能的子字符串。

捕获所有可能子串的一种方法是构造以下正则表达式,其值为n从1开始:

"(?=(\\d{" + n + "}))"

并将字符串与此字符串匹配,以增加n的值,直到没有匹配为止。

与使用“\ d +”匹配所有数字并提取所有子字符串的方法相比,此方法当然效率低下。

答案 2 :(得分:0)

仅当您指定允许的数字长度范围时才可以 O(n)

让我们说2-4位数字(数字00-9999 ):(?=(\\d{2}))(?=(\\1\\d)?)(?=(\\2\\d)?)

这是一个通过积极前瞻的零长度断言,将这种前瞻捕获成组。结果是在正则表达式输入中可以找到的所有2-4个数字字符串的数组,以及重复和空字符串(用于非匹配捕获)。

我不是Java开发人员,但我相信Perl脚本也可以作为示例阅读。

#!/usr/bin/perl                                       # perl script
use List::MoreUtils qw/ uniq /;                       # uniq subroutine library
$_ = '04/31 412-555-1235';                            # input
my @n = uniq (/(?=(\d{2}))(?=(\1\d)?)(?=(\2\d)?)/g);  # regex (single slash in Perl)
print "$_\n" for grep(/\S/, @n);                      # print non-empty lines

诀窍是使用反向引用。如果您想捕获2-5位数字符串,则需要在正则表达式中使用一个更正面的前瞻:(?=(\\d{2}))(?=(\\1\\d)?)(?=(\\2\\d)?)(?=(\\3\\d)?)

我相信这是你能做出的最接近的方法。如果这对您有用,请删除注释,并希望一些Java开发人员将使用Java代码编辑我的答案以获取上述脚本。