Сопоставление повторяющихся шаблонов Java (2)

Рассмотрим следующее регулярное выражение:

(([^\|])*\|)*([^\|]*)

Это соответствует повторяющимся шаблонам строк типа

("whatever except |" |) {0 to any times} ("whatever except |" |) {1 time}

Таким образом, он должен соответствовать следующей строке, которая имеет 17 подстрок (16 повторяющихся, плюс "z" в качестве последней).

"abcd  | e | fg | hijk | lmnop | |   | qrs |   t| uv| w |||||x   y|  z"

Действительно, RegexPal проверяет, соответствует ли данное регулярное выражение приведенной выше строке.

Теперь я хочу получить каждую из подстрок (например, «abcd |», «e |», «fg |» и т. д.), для которых нет предварительных сведений об их количестве, длине и т. д.

Согласно предыдущему сообщению StackOverflow с аналогичным названием и документации по методу Matcher class find(), я просто нужно сделать что-то вроде

Pattern pattern = Pattern.compile(regex); // regex is the above regex
Matcher matcher = pattern.matcher(input); // input is the above string

while (matcher.find())
{
   System.out.println(matcher.group(1));
}

Однако, когда я делаю это, я просто распечатываю 2 строки: последнюю повторяющуюся подстроку ("x y |") и нулевое значение; определенно не 16 подстрок, которые я ожидаю.

Неплохо было бы также проверить совпадение на самом деле перед запуском цикла find(), но я не уверен, следует ли использовать matches(), groupCount() > 0 или какое-либо другое условие, не выполняя дважды сопоставление, учитывая, что find() также выполняет сопоставление.

Итак, вопросы:

  1. Как я могу получить все 16 повторяющихся подстрок?
  2. Как я могу получить последнюю подстроку?
  3. Как проверить, что строка совпала?

person PNS    schedule 08.10.2011    source источник


Ответы (2)


Если вы должны использовать регулярное выражение...

1) Как я могу получить все 16 повторяющихся подстрок?

Смотри ниже. Когда вы переключаетесь на матчи, вам не нужно, чтобы все соответствовало, только тот раздел, который вы хотите. (Я получаю 17 совпадений - это правильно?)

2) Как я могу получить последнюю подстроку?

Переключение разделителя на начало регулярного выражения, а также разрешение «^».

3) Как проверить, что строка совпала?

Что считается несоответствием? Любая строка будет соответствовать.


Вот решение с использованием регулярных выражений:

String input = "abcd  | e | fg | hijk | lmnop | |   | qrs |   t| uv| w |||||x   y|  z";
int expectedSize = 17;
List<String> expected = new ArrayList<String>(Arrays.asList("abcd  ", " e ", " fg ", " hijk ", " lmnop ", " ", "   ", " qrs ", "   t", " uv", " w ", "",
    "", "", "", "x   y", "  z"));

List<String> matches = new ArrayList<String>();

// Pattern pattern = Pattern.compile("(?:\\||^)([^\\|]*)");
Pattern pattern = Pattern.compile("(?:_?\\||^)([^\\|]*?)(?=_?\\||$)"); // Edit: allows _| or | as delim

for (Matcher matcher = pattern.matcher(input); matcher.find();)
{
  matches.add(matcher.group(1));
}

for (int idx = 0, len = matches.size(); idx < len; idx++)
{
  System.out.format("[%-2d] \"%s\"%n", idx + 1, matches.get(idx));
}

assertSame(expectedSize, matches.size());
assertEquals(expected, matches);

Выход

[1 ] "abcd  "
[2 ] " e "
[3 ] " fg "
[4 ] " hijk "
[5 ] " lmnop "
[6 ] " "
[7 ] "   "
[8 ] " qrs "
[9 ] "   t"
[10] " uv"
[11] " w "
[12] ""
[13] ""
[14] ""
[15] ""
[16] "x   y"
[17] "  z"
person TJR    schedule 08.10.2011
comment
Большое спасибо за отличное решение! Могу я попросить немного расширить это? Перед разделителем иногда ставится символ подчеркивания (_), что дает _| между подстроками, за исключением случаев, когда подстрока пуста, и в этом случае она не отображается. Таким образом, ситуация может быть примерно такой: abcd _| е || фг _|||| Привет. Другими словами, у нас есть необязательное подчеркивание перед | и я хотел бы оставить это выключенным, когда оно появляется (оно не появляется в подстроках). Я попытался изменить ваше регулярное выражение, но то, что я придумал, не сработало. - person PNS; 09.10.2011
comment
@PNS: Так что используйте \G([^\|]+?)_?\||\G()\||\G([^\|]*)$ и получите группу, которая не является нулевой, в качестве вашего текста. Первая часть учитывает непустые данные, за которыми следует разделитель, вторая часть учитывает пустые данные, за которыми следует разделитель, а третья часть учитывает данные в конце. - person maaartinus; 09.10.2011

Боюсь, вы что-то путаете. Всякий раз, когда вы используете повторы («*», «+» и т. д.), вы не можете сопоставить все экземпляры. Используя что-то вроде ((xxx)*), вы можете сопоставить всю строку как group(1), а последнюю часть сопоставить как group(2), и ничего больше.

Рассмотрите возможность использования Разделителя Guava String.split или лучше.


Объявление 1. Вы не можете. Используйте простой шаблон, например

\G([^\|])*(\||$)

вместе с find(), чтобы получить все совпадения по порядку. Обратите внимание на привязку \G к предыдущему совпадению.


Объявление 2. Как получить последнюю подстроку?

В качестве последнего результата возвращается find.


Объявление 3. Как проверить соответствие строки?

После вашего последнего find проверьте, matcher.end() == input.length. Но с этим шаблоном ничего проверять не нужно, так как он всегда совпадает.

person maaartinus    schedule 08.10.2011
comment
Я не уверен, как это работает, но спасибо. Итак, find() перебирает все совпадения! - person PNS; 08.10.2011
comment
Моя выкройка проще вашей, так какую часть вам не хватает? \G гарантирует, что ваш следующий матч начнется сразу после окончания предыдущего. Первая группа означает любое число на не-трубах, вторая группа означает либо трубу, либо конец. Вы можете использовать \Z' or '\z вместо $. - person maaartinus; 09.10.2011