【问题标题】:Finding longest regex match in Java?在 Java 中查找最长的正则表达式匹配?
【发布时间】:2017-07-18 19:23:09
【问题描述】:

我有这个:

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 = "(?&lt;m2&gt;(hello world))|(?&lt;m1&gt;(hello|universe))" 中的组重新排序,但我无法控制我在我的情况下得到的正则表达式...

那么找到最长匹配项的最佳方法是什么?一个明显的方法是按长度检查s 的所有可能子字符串(Efficiently finding all overlapping matches for a regular expression),然后选择第一个,但那是O(n^2)。我们能做得更好吗?

【问题讨论】:

  • 使用"(?&lt;m1&gt;hello world)|(?&lt;m2&gt;hello|universe)",将最长的替代分支放在最短的之前。
  • @WiktorStribiżew:您没有阅读我的问题。我明确表示这是我无法追求的替代方案。
  • @pathikrit 如果您要动态插入交替,您有足够的控制权来重新排序(对于像这样的动态场景,我更喜欢反向字母顺序)。前几天我刚刚回答了一个类似的问题stackoverflow.com/questions/42432356/…
  • 你是个有趣的家伙,pathikrit。您想通过查看症状而不是根本原因来治愈疾病。这就像服用阿司匹林来对抗可手术脑肿瘤引起的头痛。如果正则表达式超出您的控制范围并且做错了事,要么根本不使用它,而是使用您自己的正则表达式,或者与维护正则表达式生成器的人交谈,以便让他在上游为您修复它。
  • @pathikrit,如果正则表达式总是非常简单和一致,只需编辑它并使用编辑后的版本来匹配。

标签: java regex


【解决方案1】:

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

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"

【讨论】:

  • 虽然你做了一些巧妙的优化以尽早突破,但这仍然是 O(n^2),因为matched.find() 是 O(n) 并且它处于长度为 n 的循环中跨度>
  • @pathikrit 我同意你的看法。但是,您在问题中发布的链接中的解决方案是O(n^3) 而不是O(n^2)
  • 或者,您可以find all matches of the regular expression 并从列表中选择最长的匹配项,但这可能效率不高。
【解决方案2】:

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

  1. 在最高级别有一个或多个命名组,由| 连接。
  2. 组的正则表达式放在多余的大括号中。
  3. 在这些大括号内是一个或多个由| 连接的文字。
  4. 文字从不包含|()

然后可以通过提取文字,按长度对其进行排序,然后返回第一个匹配项来编写解决方案:

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

【讨论】:

    【解决方案3】:

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

    下面的代码给出了你想要的

    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);
            }
        }
    }
    

    同样,下面的代码将跳过 hellohello 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);
            }
        }
    }
    

    【讨论】:

    • 我喜欢你的想法 :)
    • 我不认为这是正确的,例如“你好世界!”你不会匹配任何东西,但表达式应该匹配“hello”或“hello world!”应该匹配“宇宙”,但你不会再匹配任何东西。
    • 或“hello world hello”,您会找到“hello”,但您应该会找到“hello world”。您所做的基本上是endsWith(),它不能确保您找到最长的匹配项。
    【解决方案4】:

    如果正则表达式的结构始终相同,则应该可以:

    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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-04-02
      • 1970-01-01
      • 1970-01-01
      • 2020-05-06
      • 1970-01-01
      • 2012-06-29
      • 1970-01-01
      相关资源
      最近更新 更多