Czy mogę zamienić grupy w wyrażeniu regularnym Java?

101

Mam ten kod i chcę wiedzieć, czy mogę zamienić tylko grupy (nie wszystkie wzorce) w wyrażeniach regularnych Java. Kod:

 //...
 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
źródło
6
Czy możesz wyjaśnić swoje pytanie, na przykład podać oczekiwany wynik dla tego wkładu?
Michael Myers

Odpowiedzi:

128

Użyj $n(gdzie n jest cyfrą), aby odwołać się do przechwyconych podsekwencji w programie replaceFirst(...). Zakładam, że chciałeś zamienić pierwszą grupę na literalny ciąg znaków „liczba”, a drugą grupę na wartość pierwszej grupy.

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
}

Rozważ (\D+)drugą grupę zamiast (.*). *jest chciwym dopasowaniem i na początku pochłonie ostatnią cyfrę. Następnie dopasowujący będzie musiał wycofać się, gdy zda sobie sprawę, że finał (\d)nie ma nic do dopasowania, zanim będzie mógł dopasować się do ostatniej cyfry.

Chadwick
źródło
7
Byłoby miło, gdybyś opublikował przykładowe wyjście
winklerrr
6
To działa w pierwszym meczu, ale nie zadziała, jeśli jest wiele grup i przez chwilę je iterujesz (m.find ())
Hugo Zaragoza
1
Zgadzam się z Hugo, to okropny sposób na wdrożenie rozwiązania ... Dlaczego u licha jest to akceptowana odpowiedź, a nie odpowiedź acdcjunior - co jest rozwiązaniem idealnym: mała ilość kodu, duża spójność i niskie sprzężenie, znacznie mniejsza szansa (jeśli nie ma szans) niepożądanych skutków ubocznych ... wzdychaj ...
FireLight
Ta odpowiedź jest obecnie nieprawidłowa. m.replaceFirst("number $2$1");Powinno byćm.replaceFirst("number $3$1");
Daniel Eisenreich
55

Możesz użyć Matcher#start(group)i Matcher#end(group)zbudować ogólną metodę zamiany:

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

Sprawdź demo online tutaj .

acdcjunior
źródło
1
To naprawdę powinna być akceptowana odpowiedź. Jest to najbardziej kompletne i „gotowe do użycia” rozwiązanie bez wprowadzania poziomu sprzężenia z kodem towarzyszącym. Chociaż zalecałbym zmianę nazw metod jednej z nich. Na pierwszy rzut oka wygląda to jak wywołanie rekurencyjne w pierwszej metodzie.
FireLight
Utracona możliwość edycji. Odzyskaj część dotyczącą wywołania rekurencyjnego, nie przeanalizuj poprawnie kodu. Przeciążenia działają dobrze razem
FireLight
To gotowe rozwiązanie nadaje się tylko do zastąpienia pojedynczego wystąpienia i jednej grupy, a ze względu na kopiowanie całego ciągu przy każdym zamianie byłoby wysoce nieoptymalne do innych celów. Ale to dobry punkt wyjścia. Szkoda, że ​​Java zawiera wiele bzdur, ale brakuje mu podstawowych narzędzi do manipulacji ciągami.
9ilsdx 9rvj 0lo
23

Przepraszam, że biłem martwego konia, ale to trochę dziwne, że nikt tego nie zauważył - „Tak, możesz, ale to jest przeciwieństwo tego, jak używasz chwytania grup w prawdziwym życiu”.

Jeśli używasz Regex w sposób, w jaki ma być używany, rozwiązanie jest tak proste:

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

Lub jak słusznie wskazano w shmosel poniżej,

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

... ponieważ w Twoim wyrażeniu regularnym nie ma żadnego powodu, aby grupować ułamki dziesiętne.

Zwykle nie używasz grup przechwytywania na częściach łańcucha, które chcesz odrzucić , używasz ich na części łańcucha, którą chcesz zachować .

Jeśli naprawdę potrzebujesz grup, które chcesz zastąpić, prawdopodobnie zamiast tego potrzebujesz silnika szablonów (np. Wąsy, ejs, StringTemplate, ...).


Na marginesie dla ciekawskich, nawet nieprzechwytywane grupy w wyrażeniach regularnych są dostępne tylko na wypadek, gdyby silnik wyrażeń regularnych wymagał od nich rozpoznawania i pomijania tekstu zmiennego. Na przykład w

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

potrzebujesz ich, jeśli dane wejściowe mogą wyglądać na „ abcabc złap mnie bcdbcd” lub „abc capture me bcd” lub po prostu „capture me”.

Albo odwrotnie: jeśli tekst jest zawsze taki sam, a go nie przechwytujesz, nie ma żadnego powodu, aby używać grup.

Yaro
źródło
1
Grupy nieprzechwytywane są niepotrzebne; \d(.*)\dwystarczy.
shmosel
1
Nie rozumiem $11tutaj. Dlaczego 11?
Alexis
1
@Alexis - To jest dziwactwo wyrażenia regularnego w Javie: jeśli grupa 11 nie została ustawiona, java interpretuje 11 $ jako 1 $, a następnie 1.
Yaro
9

Dodaj trzecią grupę, dodając pareny wokół .*, a następnie zamień podciąg na "number" + m.group(2) + "1". na przykład:

String output = m.replaceFirst("number" + m.group(2) + "1");
mkb
źródło
4
Właściwie Matcher obsługuje odwołania w stylu 2 $, więc m.replaceFirst ("numer 21 $") zrobiłby to samo.
Michael Myers
Właściwie nie robią tego samego. "number$21"działa i "number" + m.group(2) + "1"nie działa.
Alan Moore
2
Wygląda na to, number$21że zastąpi grupę 21, a nie grupę 2 + ciąg „1”.
Fernando M. Pinheiro
To jest zwykła konkatenacja ciągów, prawda? dlaczego w ogóle musimy dzwonić do replaceFirst?
Zxcv Mnb
2

Możesz użyć metod matcher.start () i matcher.end (), aby uzyskać pozycje grupowe. Dzięki tym pozycjom możesz łatwo zastąpić dowolny tekst.

ydanneg
źródło
2

zastąp pola hasła z pola wejściowego:

{"_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, "**"));
  }
kaprys
źródło
1

Oto inne rozwiązanie, które umożliwia również zastąpienie jednej grupy w wielu meczach. Używa stosów do odwrócenia kolejności wykonywania, więc operacja na łańcuchu może być bezpiecznie wykonana.

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
źródło