Kann ich Gruppen in Java Regex ersetzen?

95

Ich habe diesen Code und möchte wissen, ob ich nur Gruppen (nicht alle Muster) in Java Regex ersetzen kann. Code:

 //...
 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
quelle
6
Können Sie Ihre Frage klären, beispielsweise die erwartete Ausgabe für diese Eingabe angeben?
Michael Myers

Antworten:

125

Verwenden Sie $n(wobei n eine Ziffer ist), um auf erfasste Teilsequenzen in zu verweisen replaceFirst(...). Ich gehe davon aus, dass Sie die erste Gruppe durch die Literalzeichenfolge "number" und die zweite Gruppe durch den Wert der ersten Gruppe ersetzen wollten .

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
}

Betrachten Sie (\D+)für die zweite Gruppe anstelle von (.*). *ist ein gieriger Matcher und verbraucht zunächst die letzte Ziffer. Der Matcher muss dann zurückverfolgen, wenn er feststellt, dass das Finale (\d)nichts zu entsprechen hat, bevor er mit der letzten Ziffer übereinstimmen kann.

Chadwick
quelle
7
Wäre schön gewesen, wenn Sie eine Beispielausgabe gepostet hätten
winklerrr
6
Dies funktioniert beim ersten Spiel, funktioniert aber nicht, wenn es viele Gruppen gibt und Sie mit einer Weile darüber iterieren (m.find ())
Hugo Zaragoza
1
Ich stimme Hugo zu, dies ist ein schrecklicher Weg, um die Lösung zu implementieren ... Warum um alles in der Welt ist dies die akzeptierte Antwort und nicht die Antwort von acdcjunior - was die perfekte Lösung ist: geringe Menge an Code, hohe Kohäsion und geringe Kopplung, viel weniger Chance (wenn nicht sogar keine Chance) von unerwünschten Nebenwirkungen ... seufz ...
Firelight
Diese Antwort ist derzeit nicht gültig. Das m.replaceFirst("number $2$1");sollte seinm.replaceFirst("number $3$1");
Daniel Eisenreich
52

Sie können eine generische Ersatzmethode verwenden Matcher#start(group)und Matcher#end(group)erstellen:

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

Überprüfen Sie die Online-Demo hier .

acdcjunior
quelle
1
Dies sollte wirklich die akzeptierte Antwort sein. Es ist die vollständigste und sofort einsatzbereite Lösung, ohne dass eine Kopplungsstufe zum zugehörigen Code eingeführt wird. Obwohl ich empfehlen würde, die Methodennamen einer dieser zu ändern. Auf den ersten Blick sieht es wie ein rekursiver Aufruf in der ersten Methode aus.
FireLight
Verpasste Bearbeitungsmöglichkeit. Nehmen Sie den Teil über den rekursiven Aufruf zurück und analysieren Sie den Code nicht richtig. Die Überladungen funktionieren gut zusammen
FireLight
23

Es tut mir leid, ein totes Pferd geschlagen zu haben, aber es ist irgendwie seltsam, dass niemand darauf hingewiesen hat - "Ja, das kannst du, aber das ist das Gegenteil davon, wie du Gruppen im wirklichen Leben fängst".

Wenn Sie Regex so verwenden, wie es verwendet werden soll, ist die Lösung so einfach:

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

Oder wie von Shmosel unten zu Recht hervorgehoben,

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

... da es in Ihrer Regex keinen guten Grund gibt, die Dezimalstellen überhaupt zu gruppieren.

Normalerweise verwenden Sie keine Erfassungsgruppen für die Teile der Zeichenfolge, die Sie verwerfen möchten . Sie verwenden sie für den Teil der Zeichenfolge, den Sie behalten möchten .

Wenn Sie wirklich Gruppen möchten, die Sie ersetzen möchten, möchten Sie wahrscheinlich stattdessen eine Vorlagen-Engine (z. B. Schnurrbart, EJS, StringTemplate, ...).


Abgesehen von den neugierigen, auch nicht erfassenden Gruppen in regulären Ausdrücken sind sie nur für den Fall da, dass die Regex-Engine sie benötigt, um variablen Text zu erkennen und zu überspringen. Zum Beispiel in

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

Sie benötigen sie, wenn Ihre Eingabe entweder wie "abcabc capture me bcdbcd" oder "abc capture me bcd" oder einfach nur "Capture me" aussehen kann .

Oder anders ausgedrückt: Wenn der Text immer derselbe ist und Sie ihn nicht erfassen, gibt es keinen Grund, überhaupt Gruppen zu verwenden.

Yaro
quelle
1
Die nicht erfassenden Gruppen sind nicht erforderlich. \d(.*)\dwird genügen.
Shmosel
1
Ich verstehe das $11hier nicht. Warum 11?
Alexis
1
@Alexis - Dies ist eine Java-Regex-Eigenart: Wenn Gruppe 11 nicht festgelegt wurde, interpretiert Java $ 11 als $ 1, gefolgt von 1.
Yaro
9

Fügen Sie eine dritte Gruppe hinzu .*, indem Sie Parens hinzufügen , und ersetzen Sie dann die Teilsequenz durch "number" + m.group(2) + "1". z.B:

String output = m.replaceFirst("number" + m.group(2) + "1");
mkb
quelle
4
Tatsächlich unterstützt Matcher den Referenzstil $ 2, sodass m.replaceFirst ("Nummer $ 21") dasselbe tun würde.
Michael Myers
Eigentlich machen sie nicht dasselbe. "number$21"funktioniert und "number" + m.group(2) + "1"nicht.
Alan Moore
2
Es sieht so aus, als number$21würde Gruppe 21 ersetzt, nicht Gruppe 2 + die Zeichenfolge "1".
Fernando M. Pinheiro
Das ist einfache Zeichenfolgenverkettung, oder? Warum müssen wir replaceFirst überhaupt aufrufen?
Zxcv Mnb
2

Sie können die Methoden matcher.start () und matcher.end () verwenden, um die Gruppenpositionen abzurufen. Mit diesen Positionen können Sie also problemlos jeden Text ersetzen.

ydanneg
quelle
1

Ersetzen Sie die Passwortfelder aus der Eingabe:

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

Hier ist eine andere Lösung, die auch das Ersetzen einer einzelnen Gruppe in mehreren Spielen ermöglicht. Es verwendet Stapel, um die Ausführungsreihenfolge umzukehren, sodass die Zeichenfolgenoperation sicher ausgeführt werden kann.

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
quelle