Извлеките все вхождения шаблона K и проверьте, соответствует ли строка " K*" за 1 проход

для данной входной строки и заданного шаблона K я хочу извлечь каждое вхождение K (или некоторую его часть (используя группы)) из строки и проверяем, что вся строка соответствует K* (поскольку он состоит из 0 или более K без других символов).

но я хотел бы сделать это за один проход С помощью регулярных выражений. Более конкретно, в настоящее время я нахожу шаблон, используя Matcher.find, но это не строго требуемый.

как бы я это сделал?

Я уже нашел решение (и опубликовал ответ), но хотел бы знать, есть ли конкретное регулярное выражение или Matcher функции которая решает / может решить этот вопрос, или просто если есть разные способы сделать это. Но даже если нет, я все равно думаю, что это интересный вопрос.

пример:

шаблон: <[0-9]> (одна цифра в <>)

допустимый: <1><2><3>

недопустимые входы:

<1><2>a<3>
<1><2>3
Oh look, a flying monkey!
<1><2><3

код, чтобы сделать это за 2 прохода с matches:

boolean products(String products)
{
    String regex = "(<[0-9]>)";
    Pattern pAll = Pattern.compile(regex + "*");

    if (!pAll.matcher(products).matches())
        return false;

    Pattern p = Pattern.compile(regex);
    Matcher matcher = p.matcher(products);

    while (matcher.find())
        System.out.println(matcher.group());

    return true;
}

4 ответов


1. Определение проблемы

так как не ясно, что выводить, когда вся строка не соответствует шаблону K*, Я переопределю проблему, чтобы прояснить, что выводить в таком случае.

учитывая любой шаблон K:

  • проверьте, что строка имеет шаблон K*.
  • если строка имеет шаблон K*, затем разделите строку на неперекрывающиеся маркеры, которые соответствуют K.
  • если строка только имеет префикс, который соответствует шаблону K*, затем выберите префикс, выбранный K*+1, и разделить префикс на токены, которые соответствуют K.

1 я не знаю, есть ли в любом случае, чтобы получить самый длинный префикс, который соответствует K. конечно, вы всегда можете удалить последний символ один за другим и протестировать против K* пока он не соответствует, но он явно неэффективен.

если не указано иное, все Я пишу ниже, будет следовать моему описанию проблемы выше. Обратите внимание, что 3-й маркер проблемы состоит в том, чтобы разрешить двусмысленность, на которой строка префикса принимать.

2. Повторная группа захвата в .NET

вышеуказанная проблема может быть решена, если у нас есть решение проблемы:

данный шаблон (K)*, который является повторной группой захвата, получить захваченный текст для всех повторений, а не только последний повторение.

  • в случае, когда строка содержит шаблон K*, путем сопоставления в отношении ^(K)*$, мы можем получить все токены, которые соответствуют шаблону K.
  • в случае, когда строка имеет только префикс, который соответствует K*, путем сопоставления в отношении ^(K)*, мы можем получить все токены, которые соответствуют шаблону K.

это имеет место в .NET regex, так как он сохраняет весь захваченный текст для повторного захвата группа.

однако, поскольку мы используем Java, у нас нет доступа к такой функции.

3. Решение на Java

проверка того, что строка имеет шаблон K* всегда можно сделать Matcher.matches()/String.matches(), так как двигатель будет делать полномасштабный откат на входной строки, чтобы как-то "унифицировать" K* в строке ввода. Трудно разделить входную строку на токены, которые соответствуют шаблону K.

если K* is эквивалентно K*+

если шаблон K имеет свойство:

для всех строк2, K* эквивалентно K*+, т. е. как входная строка разбивается на лексемы, соответствующие шаблону K - это то же самое.

2 вы можете определить это условие только для входных строк, на которых вы работаете, но обеспечение этого предварительного условия непросто. Когда вы определяете его для всех строк, вы только необходимо проанализировать регулярное выражение, чтобы проверить, выполняется ли условие или нет.

тогда может быть построено однопроходное решение, которое решает проблему. Вы можете повторно использовать Matcher.find() на схеме \GK, и проверяет, что последнее найденное совпадение находится прямо в конце строки. Это похоже на ваше текущее решение, за исключением того, что вы выполняете проверку границ с помощью кода.

на + после квантификатора * на K*+ делает квантификатор притяжательный. Притяжательный Квантор предотвратит отскок движка, что означает, что каждое повторение всегда является первым возможным совпадением для шаблона K. нам нужно это свойство, чтобы решение \GK имеет эквивалентное значение, так как он также вернет первое возможное совпадение для шаблона K.

если K* не эквивалентно K*+

без свойства выше, нам нужно 2 прохода, чтобы решить эту проблему. Первый проход для вызова Matcher.matches()/String.matches() on узор K*. На втором проходе:

  • если строка не соответствует шаблону K*, мы будем неоднократно использовать Matcher.find() на схеме \GK пока не будет найдено больше совпадений. Это можно сделать из-за того, как мы определяем, какую строку префикса принимать, когда входная строка не соответствует шаблону K*.

  • если строка соответствует шаблону K*, повторно использовать Matcher.find() на схеме \GK(?=K*$) это одно из решений. Это приведет к однако избыточная работа соответствует остальной части входной строки.

обратите внимание, что это решение универсально применимо для любого K. другими словами, оно также применяется для случая, когда K* эквивалентно K*+ (но вместо этого мы будем использовать лучшее однопроходное решение для этого случая).


вот дополнительный ответ на уже принятый. Ниже приведен пример фрагмента кода, который проходит через шаблон только один раз с m.find(), который похож на ваше однопроходное решение, но не будет анализировать несоответствующие строки.

import java.util.regex.*;

class test{
    public static void main(String args[]){
        String t = "<1><2><3>";
        Pattern pat = Pattern.compile("(<\d>)(?=(<\d>)*$)(?<=^(<\d>)*)");
        Matcher m = pat.matcher(t);
        while (m.find()) {
            System.out.println("Matches!");
            System.out.println(m.group());
        }       

    }
}

регулярное выражение, объяснил:

<\d> --Это ваш шаблон k, как определено выше
?= -- положительный lookahead (проверьте, что впереди K)
<\d>* -- матч k 0 или более раз
$ -- конец линия
?<= -- positive lookbehind (проверьте, что находится за K)
^ -- начало строки
<\d>* -- с последующим 0 или более Ks

регулярные выражения-красивые вещи.

Edit: как указал мне @nhahtdh, это просто реализованная версия ответа. На самом деле реализация выше может быть улучшена с помощью знаний в ответе.
(<\d>)(?=(<\d>)*$)(?<=^(<\d>)*) можно изменить на \G<\d>(?=(<\d>)*$).


Ниже представлено однопроходное решение с использованием Matcher.start и Matcher.end.

boolean products(String products)
{
    String regex = "<[0-9]>";

    Pattern p = Pattern.compile(regex);

    Matcher matcher = p.matcher(products);
    int lastEnd = 0;
    while (matcher.find())
    {
        if (lastEnd != matcher.start())
           return false;
        System.out.println(matcher.group());
        lastEnd = matcher.end();
    }
    if (lastEnd != products.length())
        return false;
    return true;
}

единственным недостатком является то, что он будет распечатывать (или обрабатывать) все значения до обнаружения недопустимых данных.

например, products("<1><2>a<3>"); выведет:

<1>
<2>

до создания исключения (потому что до тех пор, пока строка не будет действительна).

либо это произойдет, либо придется временно хранить все из них, кажется, неизбежно.


    String t = "<1><2><3>";
    Pattern pat = Pattern.compile("(<\d>)*");
    Matcher m = pat.matcher(t);
    if (m.matches()) {
        //String[] tt = t.split("(?<=>)"); // Look behind on '>'
        String[] tt = t.split("(?<=(<\d>))"); // Look behind on K
    }