5

是否有一种 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)in 中的方法更安全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 正则表达式匹配的所有重叠子字符串(尽管它似乎有一段时间没有更新)。具体来说,关于不间断搜索的文档指定:

使用不间断搜索,您可以找到模式的所有可能出现,包括那些相交或嵌套的模式。这是通过使用 Matcher 的方法 continue() 而不是 find() 来实现的

4

3 回答 3

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() 方法来检查我是否已经到达文本的末尾。

希望这可以帮助。谢谢!

于 2015-06-09T09:48:41.717 回答
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 代码编辑我的答案。

于 2012-07-03T11:14:23.167 回答
0

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

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

结果将在捕获组 1、2 和 3 中。

就我的想象而言,我只能将零长度断言中的捕获视为重新捕获字符串相同位置的可行方法。在零长度断言之外捕获文本将一劳永逸地消耗文本(look-behind 在 Java 中只能捕获固定长度,因此可以认为是不可访问的)。

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

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

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

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

这种方法当然比用“\d+”匹配所有数字并提取所有子串的方法效率低。

于 2012-07-03T03:21:08.310 回答