¿Puedo reemplazar grupos en Java regex?

95

Tengo este código y quiero saber si puedo reemplazar solo grupos (no todos los patrones) en Java regex. Código:

 //...
 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
fuente
6
¿Puede aclarar su pregunta, como quizás dar el resultado esperado para esa entrada?
Michael Myers

Respuestas:

125

Utilice $n(donde n es un dígito) para referirse a las subsecuencias capturadas en replaceFirst(...). Supongo que desea reemplazar el primer grupo con la cadena literal "número" y el segundo grupo con el valor del primer grupo.

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
}

Considere (\D+)para el segundo grupo en lugar de (.*). *es un comparador codicioso y al principio consumirá el último dígito. El comparador tendrá que dar marcha atrás cuando se dé cuenta de que la final (\d)no tiene nada que igualar, antes de que pueda coincidir con el dígito final.

Chadwick
fuente
7
Hubiera sido bueno si hubiera publicado una salida de ejemplo
winklerrr
6
Esto funciona en el primer partido, pero no funcionará si hay muchos grupos y los está iterando con un tiempo (m.find ())
Hugo Zaragoza
1
Estoy de acuerdo con Hugo, esta es una forma terrible de implementar la solución ... ¿Por qué diablos es esta la respuesta aceptada y no la respuesta de acdcjunior, que es la solución perfecta: pequeña cantidad de código, alta cohesión y bajo acoplamiento, muchas menos posibilidades? (si no hay posibilidad) de efectos secundarios no deseados ... suspiro ...
FireLight
Esta respuesta actualmente no es válida. The m.replaceFirst("number $2$1");should bem.replaceFirst("number $3$1");
Daniel Eisenreich
52

Puede usar Matcher#start(group)y Matcher#end(group)para construir un método de reemplazo genérico:

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, "!!!"));
}

Consulte la demostración en línea aquí .

acdcjunior
fuente
1
Esta realmente debería ser la respuesta aceptada, es la solución más completa y "lista para usar" sin introducir un nivel de acoplamiento al código adjunto. Aunque recomendaría cambiar los nombres de los métodos de uno de esos. A primera vista, parece una llamada recursiva en el primer método.
FireLight
Oportunidad de edición perdida. Retirar la parte sobre la llamada recursiva, no analizó el código correctamente. Las sobrecargas funcionan bien juntas
FireLight
23

Lamento vencer a un caballo muerto, pero es un poco extraño que nadie haya señalado esto: "Sí, puedes, pero esto es lo opuesto a cómo usas la captura de grupos en la vida real".

Si usa Regex de la forma en que debe usarse, la solución es tan simple como esta:

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

O como lo señala legítimamente shmosel a continuación,

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

... ya que en su expresión regular no hay ninguna buena razón para agrupar los decimales.

Por lo general, no usa grupos de captura en las partes de la cadena que desea descartar , los usa en la parte de la cadena que desea conservar .

Si realmente desea grupos que desea reemplazar, lo que probablemente desee en su lugar sea un motor de plantillas (por ejemplo, bigote, ejs, StringTemplate, ...).


Como un aparte para los curiosos, incluso los grupos que no capturan en las expresiones regulares están ahí para el caso de que el motor de expresiones regulares los necesite para reconocer y omitir el texto variable. Por ejemplo, en

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

que los necesite si su entrada puede buscar ya sea como "abcabc captura de mí bcdbcd" o "abc captura de mí BCD" o simplemente "la captura de mí".

O para decirlo al revés: si el texto es siempre el mismo y no lo captura, no hay ninguna razón para usar grupos.

Yaro
fuente
1
Los grupos que no capturan son innecesarios; \d(.*)\dSerá suficiente.
shmosel
1
No entiendo el $11aquí. ¿Por qué 11?
Alexis
1
@Alexis: esta es una peculiaridad de las expresiones regulares de Java: si el grupo 11 no se ha configurado, Java interpreta $ 11 como $ 1 seguido de 1.
Yaro
9

Agregue un tercer grupo agregando parens alrededor .*, luego reemplace la subsecuencia con "number" + m.group(2) + "1". p.ej:

String output = m.replaceFirst("number" + m.group(2) + "1");
mkb
fuente
4
En realidad, Matcher admite el estilo de referencia de $ 2, por lo que m.replaceFirst ("número $ 21") haría lo mismo.
Michael Myers
En realidad, no hacen lo mismo. "number$21"funciona y "number" + m.group(2) + "1"no.
Alan Moore
2
Parece number$21que reemplazaría al grupo 21, no al grupo 2 + la cadena "1".
Fernando M. Pinheiro
Esta es una simple concatenación de cadenas, ¿verdad? ¿Por qué necesitamos llamar a replaceFirst?
Zxcv Mnb
2

Puede usar los métodos matcher.start () y matcher.end () para obtener las posiciones del grupo. Entonces, usando estas posiciones, puede reemplazar fácilmente cualquier texto.

ydanneg
fuente
1

reemplace los campos de contraseña de la entrada:

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

Aquí hay una solución diferente, que también permite el reemplazo de un solo grupo en múltiples partidos. Utiliza pilas para invertir el orden de ejecución, por lo que la operación de cadena se puede ejecutar de forma segura.

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
fuente