Чи можу я замінити групи в регулярному виразі Java?


95

У мене є цей код, і я хочу знати, чи можу я замінити лише групи (не всі шаблони) в Java regex. Код:

 //...
 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.

    }

6
Чи можете ви уточнити своє запитання, як, можливо, дати очікуваний результат для цього входу?
Майкл Майерс

Відповіді:


125

Використовуйте $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
Я погоджуюся з Гюго, це жахливий спосіб втілити рішення ... Чому на Землі це прийнята відповідь, а не відповідь акддюніор - це ідеальне рішення: мала кількість коду, висока згуртованість і низька зв'язок, набагато менше шансів (якщо немає шансу) небажаних побічних ефектів ... зітхання ...
FireLight

Наразі ця відповідь недійсна. m.replaceFirst("number $2$1");Повинно бутиm.replaceFirst("number $3$1");
Daniel Eisenreich

52

Ви можете використовувати 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, "!!!"));
}

Перевірте онлайн-демонстрацію тут .


1
Це дійсно має бути прийнятою відповіддю, це найповніше і «готове до роботи» рішення, не вводячи рівень зв'язку до супровідного коду. Хоча я б рекомендував змінити назви методів одного з таких. На перший погляд це виглядає як рекурсивний виклик у першому методі.
FireLight

Пропущена можливість редагування. Поверніть частину про рекурсивний виклик, не проаналізувавши код належним чином. Перевантаження добре працюють разом
FireLight

23

Вибачте, що побили мертвого коня, але дивно, що ніхто не вказував на це - "Так, ви можете, але це навпаки, як ви користуєтесь захопленнями груп у реальному житті".

Якщо ви використовуєте Regex так, як це передбачається, рішення настільки ж просто, як це:

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

Або як справедливо зазначив шмосел нижче,

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

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

Зазвичай ви не використовуєте групи захоплення на частинах рядка, які ви хочете відкинути , ви використовуєте їх на частині рядка, яку ви хочете зберегти .

Якщо ви дійсно хочете груп, які ви хочете замінити, ви, напевно, хочете замість цього шаблону (наприклад, вуса, ejs, StringTemplate, ...).


Окрім цікавих, навіть групи, що не фіксують регулярні вирази, якраз існують для того випадку, щоб механізм регулярних виразів потребував їх для розпізнавання та пропуску тексту змінної. Наприклад, в

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

вони вам потрібні, якщо ваш внесок може виглядати як "abcabc захоплюйте мене bcdbcd" або "abc захоплюйте мене bcd" або навіть просто " захоплюйте мене".

Або кажучи навпаки: якщо текст завжди однаковий, і ви не захоплюєте його, то немає жодної причини використовувати групи.


1
Не захоплені групи непотрібні; \d(.*)\dбуде достатньо
shmosel

1
Я тут не розумію $11. Чому 11?
Алексіс,

1
@Alexis - Це химерний вислів Java: якщо група 11 не була встановлена, java інтерпретує 11 доларів як 1 долар з наступним 1.
Яро

9

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

String output = m.replaceFirst("number" + m.group(2) + "1");

4
Насправді Matcher підтримує стиль посилання на 2 долари, тому m.replaceFirst ("номер $ 21") зробить те саме.
Майкл Майєрс

Насправді вони не роблять те саме. "number$21"працює і "number" + m.group(2) + "1"ні.
Алан Мур

2
Схоже number$21, замінила б групу 21, а не групу 2 + рядок "1".
Фернандо М. Пінейро

Це звичайне з'єднання рядків, правда? чому нам взагалі потрібно викликати замінуFirst?
Zxcv Mnb

2

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


1

замініть поля пароля на вхід:

{"_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, "**"));
  }

0

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

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();       
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.