Регулярное выражение с переменным количеством групп?

Можно ли создать регулярное выражение с переменным количеством групп?

После запуска этого, например...

Pattern p = Pattern.compile("ab([cd])*ef");
Matcher m = p.matcher("abcddcef");
m.matches();

... я хотел бы иметь что-то вроде

  • m.group(1) знак равно "c"
  • m.group(2) знак равно "d"
  • m.group(3) знак равно "d"
  • m.group(4) знак равно "c",

(Справочная информация: я анализирую некоторые строки данных, и одно из "полей" повторяется. Я хотел бы избежать matcher.find цикл для этих полей.)


Как отметил @Tim Pietzcker в комментариях, perl6 и .NET имеют эту функцию.

8 ответов

Решение

Согласно документации, регулярные выражения Java не могут сделать это:

Захваченный ввод, связанный с группой, всегда является подпоследовательностью, с которой последний раз сопоставлялась группа. Если группа оценивается во второй раз из-за количественного определения, то ее ранее сохраненное значение, если таковое имеется, будет сохранено, если вторая оценка не удалась. Сопоставление строки "aba" с выражением (a(b)?)+, Например, оставляет группе два значение "b". Все захваченные данные отбрасываются в начале каждого матча.

(выделение добавлено)

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

http://download.oracle.com/javase/1,5.0/docs/api/java/lang/String.html)

Я не использовал регулярные выражения Java, но для многих языков ответ: Нет.

Кажется, что группы захвата создаются, когда регулярное выражение анализируется, и заполняется, когда оно соответствует строке. Выражение (a)|(b)(c) имеет три группы захвата, только если можно заполнить одну или две из них. (a)* имеет только одну группу, парсер оставляет последнее совпадение в группе после сопоставления.

Pattern p = Pattern.compile("ab(?:(c)|(d))*ef");
Matcher m = p.matcher("abcdef");
m.matches();

должен делать то, что вы хотите.

РЕДАКТИРОВАТЬ:

@aioobe, теперь я понимаю. Вы хотите быть в состоянии сделать что-то вроде грамматики

A    ::== <Foo> <Bars> <Baz>
Foo  ::== "foo"
Baz  ::== "baz"
Bars ::== <Bar> <Bars>
        | ε
Bar  ::== "A"
        | "B"

и вытащить все отдельные матчи Bar,

Нет, это невозможно сделать, используя java.util.regex, Вы можете использовать и использовать регулярные выражения в матче Bars или используйте генератор синтаксического анализатора, такой как ANTLR, и прикрепите побочный эффект к Bar,

У меня только что была очень похожая проблема, и мне удалось сделать "переменное число групп", но комбинацию цикла while и сброса соответствия.

    int i=0;
    String m1=null, m2=null;

    while(matcher.find(i) && (m1=matcher.group(1))!=null && (m2=matcher.group(2))!=null)
    {
        // do work on two found groups
        i=matcher.end();
    }

Но это для моей проблемы (с двумя повторяющимися

    Pattern pattern = Pattern.compile("(?<=^ab[cd]{0,100})[cd](?=[cd]{0,100}ef$)");
    Matcher matcher = pattern.matcher("abcddcef")
    int i=0;
    String res=null;

    while(matcher.find(i) && (res=matcher.group())!=null)
    {
        System.out.println(res);
        i=matcher.end();
    }

Вы теряете возможность указать произвольную длину повторения с * или же + потому что упреждающий просмотр и прогнозирование должны быть предсказуемой длины.

Если существует разумное максимальное количество совпадающих групп, с которыми вы столкнетесь:

      "ab([cd])?([cd])?([cd])?([cd])?([cd])?([cd])?([cd])?([cd])?ef"

Этот пример будет работать для 0-8 совпадений. Я признаю, что это уродливо и не читаемо по-человечески.

Я хотел бы избежать цикла matcher.find для этих полей.

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

      Pattern subPattern = Pattern.compile("[cd]");
Pattern pattern = Pattern.compile("ab(" + subPattern.pattern() + "*)ef"); // DRY, but probably safer ways to do it for the case that subPattern needs to be changed.
Matcher matcher = pattern.matcher("abccdcddef is great and all, but have you heard about abef and abddcef?");
List<String> letterSequence = new ArrayList<>();
while (matcher.find()) {
    String letters = matcher.group(1);
    Matcher subMatcher = subPattern.matcher(letters);
    while (subMatcher.find()) {
        String letter = subMatcher.group();
        letterSequence.add(letter);
    }
}
System.out.println(letterSequence);

Выход:

[в, в, г, в, г, г, д, д, в]

Я думаю, что откат препятствует такому поведению, и скажу, что эффект /([\S\s])/ в его группировке накопительное состояние на что-то вроде Библии. Даже если это можно сделать, результат непостижим, поскольку группы потеряют позиционное значение. Лучше сделать отдельное регулярное выражение для одинакового вида в глобальном смысле и поместить его в массив.

Другие вопросы по тегам