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.

    }

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.


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 .


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.


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");

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.


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

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();       
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.