在Java中找到最长的正则表达式匹配?

时间:2017-02-28 00:32:48

标签: java regex

我有这个:

import java.util.regex.*;

String regex = "(?<m1>(hello|universe))|(?<m2>(hello world))";
String s = "hello world";

Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(s);
while(matcher.find()) {
  MatchResult matchResult = m.toMatchResult();
  String substring = s.substring(matchResult.start(), matchResult.end());
  System.out.println(substring);
}

以上仅打印hello,而我希望它打印hello world

解决此问题的一种方法是重新排序String regex = "(?<m2>(hello world))|(?<m1>(hello|universe))"中的群组,但我无法控制我的情况下的正则表达式...

那么找到最长匹配的最佳方法是什么?一种显而易见的方法是按长度检查此处提到的s的所有可能子串(Efficiently finding all overlapping matches for a regular expression)并选择第一个但是O(n^2)。我们可以做得更好吗?

4 个答案:

答案 0 :(得分:3)

这是一种使用匹配器区域的方法,但是在字符串索引上有一个循环:

public static String findLongestMatch(String regex, String s) {
    Pattern pattern = Pattern.compile("(" + regex + ")$");
    Matcher matcher = pattern.matcher(s);
    String longest = null;
    int longestLength = -1;
    for (int i = s.length(); i > longestLength; i--) {
        matcher.region(0, i);
        if (matcher.find() && longestLength < matcher.end() - matcher.start()) {
            longest = matcher.group();
            longestLength = longest.length();
        }
    }
    return longest;
}

我强制模式匹配直到区域结束,然后我将区域的末端从最右边的字符串索引向左移动。对于尝试的每个区域,Java将匹配在该区域结束处完成的最左边的起始子字符串,即在该位置结束的最长子字符串。最后,这只是跟踪到目前为止发现的最长匹配的问题。

作为优化的问题,由于我从较长的区域开始向较短的区域开始,所以当所有后来的区域已经比已经找到的最长子串的长度短时,我就会停止循环。

这种方法的一个优点是它可以处理任意正则表达式,并且不需要特定的模式结构:

findLongestMatch("(?<m1>(hello|universe))|(?<m2>(hello world))", "hello world")
==> "hello world"

findLongestMatch("hello( universe)?", "hello world")
==> "hello"

findLongestMatch("hello( world)?", "hello world")
==> "hello world"

findLongestMatch("\\w+|\\d+", "12345 abc")
==> "12345"

答案 1 :(得分:2)

如果您只处理这种特定模式:

  1. |连接的最高级别上有一个或多个命名组。
  2. 该组的正则表达式放在多余的大括号中。
  3. 在这些大括号内是由|连接的一个或多个文字。
  4. 文字从不包含|()
  5. 然后可以通过提取文字,按长度排序然后返回第一个匹配来编写解决方案:

    private static final Pattern g = Pattern.compile("\\(\\?\\<[^>]+\\>\\(([^)]+)\\)\\)");
    
    public static final String findLongestMatch(String s, Pattern p) {
        Matcher m = g.matcher(p.pattern());
        List<String> literals = new ArrayList<>();
        while (m.find())
            Collections.addAll(literals, m.group(1).split("\\|"));
        Collections.sort(literals, new Comparator<String>() {
            public int compare(String a, String b) {
                return Integer.compare(b.length(), a.length());
            }
        });
        for (Iterator<String> itr = literals.iterator(); itr.hasNext();) {
             String literal = itr.next();
             if (s.indexOf(literal) >= 0)
                  return literal;
        }
        return null;
    }
    

    测试:

    System.out.println(findLongestMatch(
        "hello world",
        Pattern.compile("(?<m1>(hello|universe))|(?<m2>(hello world))")
    ));
    // output: hello world
    System.out.println(findLongestMatch(
        "hello universe",
        Pattern.compile("(?<m1>(hello|universe))|(?<m2>(hello world))")
    ));
    // output: universe
    

答案 2 :(得分:1)

  

只需在Or分隔符$之前添加|(字符串结尾)   然后检查字符串是否结束。如果结束,它将返回字符串。否则跳过正则表达式的那部分。

以下代码提供了您想要的内容

import java.util.regex.*;
public class RegTest{
  public static void main(String[] arg){
        String regex = "(?<m1>(hello|universe))$|(?<m2>(hello world))";
        String s = "hello world";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(s);
        while(matcher.find()) {
            MatchResult matchResult = matcher.toMatchResult();
            String substring = s.substring(matchResult.start(), matchResult.end());
            System.out.println(substring);
        }
    }
}
  

同样,以下代码将跳过 hello hello world 并匹配 hello world
  查看$那里

的用法
import java.util.regex.*;
public class RegTest{
  public static void main(String[] arg){
        String regex = "(?<m1>(hello|universe))$|(?<m2>(hello world))$|(?<m3>(hello world there))";
        String s = "hello world there";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(s);
        while(matcher.find()) {
            MatchResult matchResult = matcher.toMatchResult();
            String substring = s.substring(matchResult.start(), matchResult.end());
            System.out.println(substring);
        }
    }
}

答案 3 :(得分:1)

如果正则表达式的结构总是相同的,这应该有效:

String regex = "(?<m1>(hello|universe))|(?<m2>(hello world))";
String s = "hello world";

//split the regex into the different groups
String[] allParts = regex.split("\\|\\(\\?\\<");
for (int i=1; i<allParts.length; i++) {
    allParts[i] = "(?<" + allParts[i];
}

//find the longest string
int longestSize = -1;
String longestString = null;
for (int i=0; i<allParts.length; i++) {
    Pattern pattern = Pattern.compile(allParts[i]);
    Matcher matcher = pattern.matcher(s);
    while(matcher.find()) {
        MatchResult matchResult = matcher.toMatchResult();
        String substring = s.substring(matchResult.start(), matchResult.end());
        if (substring.length() > longestSize) {
            longestSize = substring.length();
            longestString = substring;
        }
    }
}
System.out.println("Longest: " + longestString);