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

У меня есть этот код, и я хочу знать, могу ли я заменить только группы (не все шаблоны) в 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 ответов


использовать $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 ");  // number 46
}

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


можно использовать 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, "!!!"));
}

Регистрация онлайн демо здесь.


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

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

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

Если вы используете регулярное выражение так, как оно должно использоваться, решение так просто:

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

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

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

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

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

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


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

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

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

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


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


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

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();       
}