Поиск самого длинного регулярного выражения в Java?

У меня есть это:

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, как указано здесь (Эффективно найти все совпадающие соответствия для регулярного выражения) по длине и выбрать первый, но O(n^2). Можем ли мы лучше?

Ответ 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"

Ответ 2

Если вы имеете дело только с этим конкретным шаблоном:

  • На самом высоком уровне есть одна или несколько названных групп, связанных |.
  • Регулярное выражение для группы помещается в лишние фигурные скобки.
  • Внутри этих фигурных скобок есть один или несколько литералов, связанных |.
  • Литералы никогда не содержат |, ( или ).

Тогда можно написать решение, извлекая литералы, сортируя их по их длине и затем возвращая первое совпадение:

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

Аналогично, нижеприведенный код будет пропускать привет, привет мир и соответствовать миру привет там
См. Использование $ there

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

Ответ 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);