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

102

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

 //...
 Pattern p = Pattern.compile("(\\d).*(\\d)");
    String input = "6 example input 4";
    Matcher m = p.matcher(input);
    if (m.find()) {

        //Now I want replace group one ( (\\d) ) with number 
       //and group two (too (\\d) ) with 1, but I don't know how.

    }
Wokena
источник
6
Не могли бы вы прояснить свой вопрос, например, дать ожидаемый результат для этого ввода?
Майкл Майерс

Ответы:

128

Используйте $n(где n - цифра) для ссылки на захваченные подпоследовательности в replaceFirst(...). Я предполагаю, что вы хотели заменить первую группу буквальной строкой «число», а вторую группу - значением первой группы.

Pattern p = Pattern.compile("(\\d)(.*)(\\d)");
String input = "6 example input 4";
Matcher m = p.matcher(input);
if (m.find()) {
    // replace first number with "number" and second number with the first
    String output = m.replaceFirst("number $3$1");  // number 46
}

Считайте (\D+)для второй группы вместо (.*). *является жадным сопоставителем и сначала будет использовать последнюю цифру. Затем сопоставителю придется вернуться назад, когда он поймет, что в финале (\d)нет ничего для сопоставления, прежде чем он сможет сопоставить с последней цифрой.

Чедвик
источник
7
Было бы неплохо, если бы вы опубликовали пример вывода
winklerrr
6
Это работает с первым совпадением, но не сработает, если групп много и вы некоторое время повторяете их (m.find ())
Уго Сарагоса,
1
Я согласен с Хьюго, это ужасный способ реализовать решение ... Почему, черт возьми, это принятый ответ, а не ответ acdcjunior, который является идеальным решением: небольшой объем кода, высокая согласованность и низкая взаимосвязь, гораздо меньше шансов (если нет никаких шансов) нежелательных побочных эффектов ... вздох ...
FireLight
Этот ответ в настоящее время недействителен. m.replaceFirst("number $2$1");Должно бытьm.replaceFirst("number $3$1");
Daniel Eisenreich
56

Вы можете использовать Matcher#start(group)и Matcher#end(group)для создания универсального метода замены:

public static String replaceGroup(String regex, String source, int groupToReplace, String replacement) {
    return replaceGroup(regex, source, groupToReplace, 1, replacement);
}

public static String replaceGroup(String regex, String source, int groupToReplace, int groupOccurrence, String replacement) {
    Matcher m = Pattern.compile(regex).matcher(source);
    for (int i = 0; i < groupOccurrence; i++)
        if (!m.find()) return source; // pattern not met, may also throw an exception here
    return new StringBuilder(source).replace(m.start(groupToReplace), m.end(groupToReplace), replacement).toString();
}

public static void main(String[] args) {
    // replace with "%" what was matched by group 1 
    // input: aaa123ccc
    // output: %123ccc
    System.out.println(replaceGroup("([a-z]+)([0-9]+)([a-z]+)", "aaa123ccc", 1, "%"));

    // replace with "!!!" what was matched the 4th time by the group 2
    // input: a1b2c3d4e5
    // output: a1b2c3d!!!e5
    System.out.println(replaceGroup("([a-z])(\\d)", "a1b2c3d4e5", 2, 4, "!!!"));
}

Посмотреть онлайн-демо здесь .

acdcjunior
источник
2
Это действительно должен быть принятый ответ, это наиболее полное и «готовое к работе» решение без введения уровня связи с сопроводительным кодом. Хотя я бы порекомендовал изменить имена методов одного из них. На первый взгляд это выглядит как рекурсивный вызов первого метода.
FireLight
Упущенная возможность редактирования. Верните часть о рекурсивном вызове, неправильно проанализировали код. Перегрузки хорошо работают вместе
FireLight
Это готовое решение подходит только для замены одного вхождения и одной группы, и из-за копирования полной строки при каждой замене было бы крайне неоптимальным для любых других целей. Но это хорошая отправная точка. Жалко, что в Java много чепухи, но в ней отсутствуют базовые средства обработки строк.
9ilsdx 9rvj 0lo
25

Извините, что побил мертвую лошадь, но это как-то странно, что никто не указал на это: «Да, вы можете, но это противоположно тому, как вы используете группы захвата в реальной жизни».

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

"6 example input 4".replaceAll("(?:\\d)(.*)(?:\\d)", "number$11");

Или, как справедливо указал ниже Шмосел,

"6 example input 4".replaceAll("\d(.*)\d", "number$11");

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

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

Если вам действительно нужны группы, которые вы хотите заменить, то, вероятно, вы захотите использовать механизм шаблонов (например, mustache, ejs, StringTemplate, ...).


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

(?:abc)*(capture me)(?:bcd)*

они вам нужны, если ваш ввод может выглядеть как «abcabc capture me bcdbcd» или «abc capture me bcd» или даже просто «захватить меня».

Или, говоря наоборот: если текст всегда один и тот же, и вы его не фиксируете, нет причин использовать группы вообще.

Яро
источник
1
Не захватывающие группы не нужны; \d(.*)\dбудет достаточно.
shmosel
1
Я не понимаю $11здесь. Почему 11?
Alexis
1
@Alexis - Это причуда регулярного выражения Java: если группа 11 не была установлена, Java интерпретирует 11 долларов как 1 доллар, за которым следует 1.
Яро
9

Добавьте третью группу, добавив скобки вокруг .*, затем замените подпоследовательность на "number" + m.group(2) + "1". например:

String output = m.replaceFirst("number" + m.group(2) + "1");
мкб
источник
4
На самом деле Matcher поддерживает стиль ссылки $ 2, поэтому m.replaceFirst ("number $ 21") будет делать то же самое.
Майкл Майерс
На самом деле они не делают то же самое. "number$21"работает и "number" + m.group(2) + "1"не работает.
Алан Мур
2
Похоже number$21, заменит группу 21, а не группу 2 + строку «1».
Fernando M. Pinheiro
Это простая конкатенация строк, верно? зачем вообще вызывать replaceFirst?
Zxcv Mnb
2

Вы можете использовать методы matcher.start () и matcher.end () для получения позиций групп. Таким образом, используя эти позиции, вы легко можете заменить любой текст.

Ydanneg
источник
2

замените поля пароля из ввода:

{"_csrf":["9d90c85f-ac73-4b15-ad08-ebaa3fa4a005"],"originPassword":["uaas"],"newPassword":["uaas"],"confirmPassword":["uaas"]}



  private static final Pattern PATTERN = Pattern.compile(".*?password.*?\":\\[\"(.*?)\"\\](,\"|}$)", Pattern.CASE_INSENSITIVE);

  private static String replacePassword(String input, String replacement) {
    Matcher m = PATTERN.matcher(input);
    StringBuffer sb = new StringBuffer();
    while (m.find()) {
      Matcher m2 = PATTERN.matcher(m.group(0));
      if (m2.find()) {
        StringBuilder stringBuilder = new StringBuilder(m2.group(0));
        String result = stringBuilder.replace(m2.start(1), m2.end(1), replacement).toString();
        m.appendReplacement(sb, result);
      }
    }
    m.appendTail(sb);
    return sb.toString();
  }

  @Test
  public void test1() {
    String input = "{\"_csrf\":[\"9d90c85f-ac73-4b15-ad08-ebaa3fa4a005\"],\"originPassword\":[\"123\"],\"newPassword\":[\"456\"],\"confirmPassword\":[\"456\"]}";
    String expected = "{\"_csrf\":[\"9d90c85f-ac73-4b15-ad08-ebaa3fa4a005\"],\"originPassword\":[\"**\"],\"newPassword\":[\"**\"],\"confirmPassword\":[\"**\"]}";
    Assert.assertEquals(expected, replacePassword(input, "**"));
  }
прихоть
источник
1

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

private static void demo () {

    final String sourceString = "hello world!";

    final String regex = "(hello) (world)(!)";
    final Pattern pattern = Pattern.compile(regex);

    String result = replaceTextOfMatchGroup(sourceString, pattern, 2, world -> world.toUpperCase());
    System.out.println(result);  // output: hello WORLD!
}

public static String replaceTextOfMatchGroup(String sourceString, Pattern pattern, int groupToReplace, Function<String,String> replaceStrategy) {
    Stack<Integer> startPositions = new Stack<>();
    Stack<Integer> endPositions = new Stack<>();
    Matcher matcher = pattern.matcher(sourceString);

    while (matcher.find()) {
        startPositions.push(matcher.start(groupToReplace));
        endPositions.push(matcher.end(groupToReplace));
    }
    StringBuilder sb = new StringBuilder(sourceString);
    while (! startPositions.isEmpty()) {
        int start = startPositions.pop();
        int end = endPositions.pop();
        if (start >= 0 && end >= 0) {
            sb.replace(start, end, replaceStrategy.apply(sourceString.substring(start, end)));
        }
    }
    return sb.toString();       
}
Jonas_Hess
источник