Регулярное выражение с переменным количеством групп?
Можно ли создать регулярное выражение с переменным количеством групп?
После запуска этого, например...
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])/
в его группировке накопительное состояние на что-то вроде Библии. Даже если это можно сделать, результат непостижим, поскольку группы потеряют позиционное значение. Лучше сделать отдельное регулярное выражение для одинакового вида в глобальном смысле и поместить его в массив.