【问题标题】:All overlapping substrings matching a java regex与 java 正则表达式匹配的所有重叠子字符串
【发布时间】:2012-07-03 10:28:04
【问题描述】:

是否有返回所有(可能重叠)匹配正则表达式的子字符串的 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) 算法可以枚举并检查所有子字符串是否符合模式。

编辑

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

编辑 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)
    }
  }
}

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

编辑 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 指定:

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

【问题讨论】:

  • 只需对所有 3 个或更多字符结果进行正则表达式后循环
  • regexlib.com 可能是一个进行挖掘的好地方。
  • @Ωmega 尽我所能,但欢迎无用的反馈。干杯。
  • 我认为正则表达式不会进行重复扫描。大海捞针中的角色只能使用一次。我能想到的最接近的方法是不贪婪的匹配。但这只会返回 12、35 而不会返回 1235。

标签: java regex matcher


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

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

【讨论】:

    【解决方案2】:

    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 代码编辑我的答案。

    【讨论】:

    • Java 中的正则表达式是一样的(除了反斜杠需要转义)。至于uniq,可以用Java中的Set模拟(TreeSetHashSet)。
    • @nhahtdh - 谢谢。随时通过编辑帖子来更新我的答案。
    【解决方案3】:

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

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

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

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

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-16
      • 2021-08-20
      • 2012-08-15
      • 1970-01-01
      • 2022-01-17
      • 1970-01-01
      相关资源
      最近更新 更多