Posso sostituire i gruppi in Java regex?


95

Ho questo codice e voglio sapere se posso sostituire solo i gruppi (non tutti i pattern) in Java regex. Codice:

 //...
 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
Puoi chiarire la tua domanda, come forse dare l'output atteso per quell'input?
Michael Myers

Risposte:


125

Utilizzare $n(dove n è una cifra) per fare riferimento alle sottosequenze acquisite in replaceFirst(...). Suppongo che tu voglia sostituire il primo gruppo con la stringa letterale "numero" e il secondo gruppo con il valore del primo gruppo.

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
}

Considera (\D+)per il secondo gruppo invece di (.*). *è un avido abbinatore e all'inizio consumerà l'ultima cifra. Il matcher dovrà quindi tornare sui propri passi quando si renderà conto che la finale (\d)non ha nulla da abbinare, prima che possa corrispondere alla cifra finale.


7
Sarebbe stato bello se avessi pubblicato un output di esempio
winklerrr

6
Funziona sulla prima partita, ma non funzionerà se ci sono molti gruppi e stai ripetendo su di loro con un po 'di tempo (m.find ())
Hugo Zaragoza

1
Sono d'accordo con Hugo, questo è un modo terribile per implementare la soluzione ... Perché sulla Terra questa è la risposta accettata e non la risposta di acdcjunior - che è la soluzione perfetta: piccola quantità di codice, alta coesione e basso accoppiamento, molte meno possibilità (se non nessuna possibilità) di effetti collaterali indesiderati ... sigh ...
FireLight

Questa risposta non è attualmente valida. Il m.replaceFirst("number $2$1");dovrebbe esserem.replaceFirst("number $3$1");
Daniel Eisenreich

52

Potresti usare Matcher#start(group)e Matcher#end(group)per costruire un metodo di sostituzione generico:

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

Controlla la demo online qui .


1
Questa dovrebbe davvero essere la risposta accettata è la soluzione più completa e "pronta all'uso" senza introdurre un livello di accoppiamento al codice di accompagnamento. Anche se consiglierei di cambiare i nomi dei metodi di uno di questi. A prima vista sembra una chiamata ricorsiva nel primo metodo.
FireLight

Opportunità di modifica persa. Riprendi la parte relativa alla chiamata ricorsiva, non ha analizzato correttamente il codice. I sovraccarichi funzionano bene insieme
FireLight

23

Mi spiace battere un cavallo morto, ma è piuttosto strano che nessuno lo abbia fatto notare: "Sì, puoi, ma questo è l'opposto di come usi la cattura di gruppi nella vita reale".

Se usi Regex nel modo in cui deve essere utilizzato, la soluzione è semplice come questa:

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

O come giustamente sottolineato da shmosel di seguito,

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

... poiché nella tua regex non c'è nessuna buona ragione per raggruppare i decimali.

Di solito non usi i gruppi di cattura sulle parti della stringa che vuoi scartare , li usi sulla parte della stringa che vuoi mantenere .

Se vuoi davvero i gruppi che vuoi sostituire, quello che probabilmente vuoi invece è un motore di template (es. Moustache, ejs, StringTemplate, ...).


Per inciso per i curiosi, anche i gruppi che non catturano le espressioni regolari sono lì solo per il caso in cui il motore delle espressioni regolari ha bisogno di loro per riconoscere e saltare il testo variabile. Ad esempio, in

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

ne hai bisogno se il tuo input può apparire come "abcabc capture me bcdbcd" o "abc capture me bcd" o anche solo "cattura me".

O per dirla al contrario: se il testo è sempre lo stesso e non lo catturi, non c'è motivo di usare i gruppi.


1
I gruppi di non acquisizione non sono necessari; \d(.*)\dsarà sufficiente.
shmosel

1
Non capisco $11qui. Perché 11?
Alexis

1
@Alexis - Questa è una stranezza delle espressioni regolari java: se il gruppo 11 non è stato impostato, java interpreta $ 11 come $ 1 seguito da 1.
Yaro

9

Aggiungi un terzo gruppo aggiungendo parentesi intorno .*, quindi sostituisci la sottosequenza con "number" + m.group(2) + "1". per esempio:

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

4
In realtà, Matcher supporta lo stile di riferimento $ 2, quindi m.replaceFirst ("number $ 21") farebbe la stessa cosa.
Michael Myers

In realtà, non fanno la stessa cosa. "number$21"funziona e "number" + m.group(2) + "1"non funziona.
Alan Moore

2
Sembra number$21che sostituisca il gruppo 21, non il gruppo 2 + la stringa "1".
Fernando M. Pinheiro

Questa è una semplice concatenazione di stringhe, giusto? perché dobbiamo chiamare replaceFirst?
Zxcv Mnb

2

Puoi usare i metodi matcher.start () e matcher.end () per ottenere le posizioni del gruppo. Quindi usando queste posizioni puoi facilmente sostituire qualsiasi testo.


1

sostituire i campi della password dall'input:

{"_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

Ecco una soluzione diversa, che permette anche la sostituzione di un singolo gruppo in più partite. Utilizza gli stack per invertire l'ordine di esecuzione, in modo che l'operazione di stringa possa essere eseguita in sicurezza.

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();       
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.