Замена неоднократно встречающихся групп привязанного регулярного выражения в java

#java #regex

#java #регулярное выражение

Вопрос:

Используя Java 7 и реализацию регулярных выражений по умолчанию в java.util.regex.Pattern, учитывая регулярное выражение, подобное этому:

^start (m[aei]ddel[0-9] ?) tail$

И строка, подобная этой:

start maddel1 meddel2 middel3 tail

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

start <match> <match> <match> tail .

Я могу получить каждую группу без таких якорей:

Регулярное выражение: m[aei]ddel[0-9]

 StringBuffer sb = new StringBuffer();
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
    matcher.appendReplacement(sb, Matcher.quoteReplacement("<middle>"));
}
  

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

Однако, когда я добавляю якоря, единственный API, который я могу найти, требует полного совпадения и доступа к последнему вхождению группы. В моем случае мне нужно убедиться, что регулярное выражение действительно совпадает (т. Е. Полностью совпадает), но на этапе замены мне нужно иметь возможность доступа к каждой группе самостоятельно.

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

Комментарии:

1. Вы просто заменяете каждую maddle на <middle> ?

2. да, я не думаю, что мне нужно выполнять больше задач для согласованной последовательности

3. Чтобы уточнить: m[aei]ddel[0-9] это всего лишь пример, мои фактические регулярные выражения более сложные, и каждое регулярное выражение будет иметь другую замену. Важным моментом является то, что мне нужно заменять каждое вхождение группы соответствия на ее собственную замену, и регулярное выражение должно быть привязано.

4. Вы можете сделать это в два этапа: сопоставьте привязанное предложение, затем замените сгруппированные слова в этом предложении.

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

Ответ №1:

Вы можете использовать G для этого:

 final String regex = "(^start |(?<!^)\G)m[aei]ddel[0-9] (?=.* tail$)";
final String str = "start maddel1 meddel2 middel3 tail";

String repl = str.replaceAll(regex, "$1<match> ");
//=> start <match> <match> <match> tail
  

Демонстрация регулярных выражений

G утверждает позицию в конце предыдущего совпадения или в начале строки для первого совпадения.

Комментарии:

1. Это регулярное выражение также найдет совпадения в строке типа start maddel1 meddel2 middel3 something else tail

Ответ №2:

Чтобы сделать это за один шаг, вам нужно использовать G регулярное выражение на основе, которое будет выполнять привязку. Однако вам также нужен положительный прогноз, чтобы проверить, заканчивается ли строка желаемым шаблоном.

Вот регулярное выражение, которое должно работать:

 (^start|(?!A)G)s m[aei]ddel[0-9](?=(?:s m[aei]ddel[0-9])*s tail$)
  

Смотрите демонстрацию регулярных выражений

 String s = "start maddel1 meddel2 middel3 tail";
String pat = "(^start|(?!\A)\G)\s (m[aei]ddel[0-9])(?=(?:\s m[aei]ddel[0-9])*\s tail$)";
System.out.println(s.replaceAll(pat, "$1 <middle>" )); 
  

Смотрите онлайн-демонстрацию Java

Объяснение:

  • (^start|(?!A)G) — совпадение start в конце строки или в конце предыдущего успешного совпадения
  • s — 1 или более пробелов
  • m[aei]ddel[0-9] m , затем либо a , e , i , затем ddel , затем 1 цифра
  • (?=(?:s m[aei]ddel[0-9])*s tail$) — только если за ним следует:
    • (?:s m[aei]ddel[0-9])* — ноль или более последовательностей из 1 пробелов и middelN шаблона
    • s — 1 или более пробелов
    • tail$ tails подстрока, за которой следует конец строки.

Комментарии:

1. Хороший вызов, что регулярное выражение anubhava также будет соответствовать другим входным данным. Жаль, что все становится намного сложнее (с точки зрения синтаксиса и требует обходных путей). Я попробую и надеюсь, что мои товарищи по команде смогут жить с немного более пугающими регулярными выражениями 😉

2. Если вы не хотите жестко кодировать пробелы, переместите шаблон пробелов в группу 1, см. ideone.com/5nPrri . Вы все равно можете использовать двухэтапный подход, сначала заменив строку на "^start .* tail\z" , а затем заменив то, что вам нужно.

Ответ №3:

С G помощью привязки для find метода вы можете записать его следующим образом:

 pat = "\G(?:(?!\A) |\Astart (?=(?:m[aei]ddel[0-9] ) tail\z))(m\S )";
  

Подробные сведения:

 \G # position after the previous match or at the start of the string
    # putting it in factor makes fail the pattern more quickly after the last match
(?:
    (?!\A) [ ] # a space not at the start of the string
                # this branch is the first one because it has more chance to succeed
  |
    \A start [ ] # "start " at the beginning of the string
    (?=(?:m[aei]ddel[0-9] ) tail\z) # check the string format once and for all
                                     # since this branch will succeed only once
)
( # capture group 1
    m\S  # the shortest and simplest pattern that matches "m[aei]ddel[0-9]"
          # and excludes "tail" (adapt it to your need but keep the same idea)
)
  

ДЕМОНСТРАЦИЯ

Комментарии:

1. Честно говоря, мне нравятся ваши решения.