Java regex'indeki grupları değiştirebilir miyim?


102

Bu koda sahibim ve Java regex'te yalnızca grupları (tüm kalıbı değil) değiştirip değiştiremeyeceğimi bilmek istiyorum. 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
Sorunuzu netleştirebilir misiniz, mesela bu girdi için beklenen çıktıyı verebilir misiniz?
Michael Myers

Yanıtlar:


128

İçinde $nyakalanan alt dizileri belirtmek için (burada n bir rakamdır) kullanın replaceFirst(...). İlk grubu "sayı" harf dizisiyle ve ikinci grubu birinci grubun değeriyle değiştirmek istediğinizi varsayıyorum .

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
}

(\D+)Bunun yerine ikinci grubu düşünün (.*). *açgözlü bir eşleştiricidir ve ilk başta son rakamı tüketir. Eşleştirici (\d), finalin son rakamla eşleşmeden önce eşleşecek hiçbir şeyi olmadığını anladığında geri adım atmak zorunda kalacaktır .


7
Örnek bir çıktı
göndermiş

6
Bu ilk maçta işe yarıyor, ancak çok sayıda grup varsa ve bunları bir süre tekrarlıyorsanız işe yaramayacak (m.find ())
Hugo Zaragoza

1
Hugo ile aynı fikirdeyim, bu çözümü uygulamanın korkunç bir yolu ... Neden bu kabul edilen cevap ve acdcjunior'un cevabı değil - ki bu mükemmel çözüm: az miktarda kod, yüksek uyum ve düşük bağlantı, çok daha az şans (şansı yoksa) istenmeyen yan etkiler ... iç çekme ...
FireLight

Bu cevap şu anda geçerli değil. m.replaceFirst("number $2$1");Olmalım.replaceFirst("number $3$1");
Daniel Eisenreich

55

Genel bir değiştirme yöntemi oluşturmak için Matcher#start(group)ve kullanabilirsiniz Matcher#end(group):

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

Çevrimiçi demoyu buradan kontrol edin .


1
Bu gerçekten kabul edilen cevap olmalı, beraberindeki koda bir düzeyde birleştirme getirmeden en eksiksiz ve "kullanıma hazır" çözümdür. Yine de bunlardan birinin yöntem adlarını değiştirmenizi tavsiye ederim. İlk bakışta, ilk yöntemde özyinelemeli bir çağrı gibi görünür.
FireLight

Kaçırılan düzenleme fırsatı. Özyinelemeli çağrı ile ilgili kısmı geri alın, kodu doğru şekilde analiz etmediniz. Aşırı yükler birlikte iyi çalışıyor
FireLight

Kutudan çıktığı gibi bu çözüm, yalnızca tek bir oluşumun ve bir grubun yerini almaya uygundur ve her değiştirmeyle birlikte tam dizenin kopyalanması, diğer herhangi bir amaç için oldukça yetersiz olacaktır. Ama bu iyi bir başlangıç ​​noktası. Yazık bir Java, pek çok saçma sapan şapka, ancak temel dizgi işleme olanaklarından yoksun.
9ilsdx 9rvj 0lo

23

Ölü bir atı dövdüğüm için özür dilerim, ama hiç kimsenin buna işaret etmemesi biraz garip - "Evet yapabilirsin, ama bu gerçek hayatta grupları yakalama şeklinin tam tersi".

Regex'i kullanılması gerektiği gibi kullanırsanız, çözüm şu kadar basittir:

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

Veya aşağıdaki shmosel'in haklı olarak işaret ettiği gibi,

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

... normal ifadenizde ondalık sayıları gruplamak için iyi bir neden olmadığından.

Genellikle dizinin atmak istediğiniz kısımlarında yakalama grupları kullanmazsınız, onları dizenin tutmak istediğiniz kısmında kullanırsınız .

Değiştirmek istediğiniz grupları gerçekten istiyorsanız, muhtemelen bunun yerine bir şablon oluşturma motorudur (örn. Bıyık, ejs, StringTemplate, ...).


Meraklılar için bir kenara olarak, normal ifadelerdeki yakalamayan gruplar bile, normal ifade motorunun değişken metni tanıması ve atlaması için ihtiyaç duyduğu durum için oradadır. Örneğin,

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

girişinizi ya "abcabc'ait benzeyebilir eğer onlara ihtiyaç beni yakalama bcdbcd" veya "abc beni yakalama bcd" hatta sadece "yakalama bana".

Ya da başka bir deyişle: Metin her zaman aynıysa ve onu yakalayamazsanız, grupları kullanmak için hiçbir neden yoktur.


1
Yakalamayan gruplar gereksizdir; \d(.*)\dyeterli olacaktır.
shmosel

1
Anlamıyorum $11burada. Neden 11?
Alexis

1
@Alexis - Bu bir java regex tuhaflığıdır: grup 11 ayarlanmamışsa, java 11 $ 'ı 1 $ ve ardından 1 $ olarak yorumlar
Yaro

9

Etrafına parantez ekleyerek üçüncü bir grup ekleyin .*, ardından alt diziyi ile değiştirin "number" + m.group(2) + "1". Örneğin:

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

4
Aslında, Matcher $ 2 referans stilini destekler, bu nedenle m.replaceFirst ("sayı 21 $") aynı şeyi yapacaktır.
Michael Myers

Aslında onlar değil aynı şeyi yapın. "number$21"çalışır ve "number" + m.group(2) + "1"çalışmaz.
Alan Moore

2
Görünüşe göre number$21grup 21'in yerine geçecek, grup 2'nin değil + "1" dizesi.
Fernando M. Pinheiro

Bu düz dizgi birleştirme, değil mi? neden önce replaceFirst'i aramamız gerekiyor?
Zxcv Mnb

2

Grup konumlarını almak için matcher.start () ve matcher.end () yöntemlerini kullanabilirsiniz. Dolayısıyla, bu konumları kullanarak herhangi bir metni kolayca değiştirebilirsiniz.


2

girişteki şifre alanlarını değiştirin:

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

İşte tek bir grubun birden çok maçta değiştirilmesine de izin veren farklı bir çözüm. Yürütme sırasını tersine çevirmek için yığınları kullanır, böylece dize işlemi güvenli bir şekilde yürütülebilir.

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();       
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.