Bisakah saya mengganti grup di regex Java?

95

Saya memiliki kode ini, dan saya ingin tahu, apakah saya hanya dapat mengganti grup (tidak semua pola) di regex Java. Kode:

 //...
 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
sumber
6
Dapatkah Anda memperjelas pertanyaan Anda, seperti mungkin memberikan keluaran yang diharapkan untuk masukan tersebut?
Michael Myers

Jawaban:

125

Gunakan $n(di mana n adalah digit) untuk merujuk ke bagian selanjutnya yang diambil replaceFirst(...). Saya berasumsi Anda ingin mengganti grup pertama dengan string literal "number" dan grup kedua dengan nilai grup pertama.

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
}

Pertimbangkan (\D+)untuk kelompok kedua, bukan (.*). *adalah pencocokkan yang rakus, dan akan mengonsumsi digit terakhir pada awalnya. Pencocokan kemudian harus mundur ketika menyadari final (\d)tidak ada yang cocok, sebelum bisa cocok dengan digit terakhir.

Chadwick
sumber
7
Akan lebih baik jika Anda akan memposting keluaran contoh
winklerrr
6
Ini berfungsi pada pertandingan pertama, tetapi tidak akan berhasil jika ada banyak grup dan Anda mengulanginya sebentar (m.find ())
Hugo Zaragoza
1
Saya Setuju dengan Hugo, ini adalah cara yang buruk untuk menerapkan solusi ... Mengapa di Bumi ini jawaban yang diterima dan bukan jawaban acdcjunior - yang merupakan solusi sempurna: sejumlah kecil kode, kohesi tinggi dan kopling rendah, peluang jauh lebih kecil (jika tidak ada kemungkinan) dari efek samping yang tidak diinginkan ... mendesah ...
FireLight
Jawaban ini saat ini tidak valid. The m.replaceFirst("number $2$1");harusm.replaceFirst("number $3$1");
Daniel Eisenreich
52

Anda dapat menggunakan Matcher#start(group)dan Matcher#end(group)membuat metode penggantian umum:

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

Cek demo online di sini .

acdcjunior.dll
sumber
1
Ini benar-benar harus menjadi jawaban yang diterima itu adalah solusi yang paling lengkap dan "siap digunakan" tanpa memperkenalkan tingkat penggabungan ke kode yang menyertainya. Meskipun saya akan merekomendasikan mengubah nama metode salah satunya. Sekilas, ini terlihat seperti panggilan rekursif pada metode pertama.
FireLight
Peluang edit terlewatkan. Ambil kembali bagian tentang panggilan rekursif, tidak menganalisis kode dengan benar. Kelebihan beban bekerja sama dengan baik
FireLight
23

Maaf untuk mengalahkan kuda mati, tapi agak aneh bahwa tidak ada yang menunjukkan ini - "Ya Anda bisa, tapi ini kebalikan dari bagaimana Anda menggunakan grup penangkap dalam kehidupan nyata".

Jika Anda menggunakan Regex seperti seharusnya, solusinya sesederhana ini:

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

Atau seperti yang ditunjukkan oleh symosel di bawah ini,

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

... karena di regex Anda tidak ada alasan yang baik untuk mengelompokkan desimal sama sekali.

Anda biasanya tidak menggunakan grup penangkap pada bagian string yang ingin Anda buang , Anda menggunakannya pada bagian string yang ingin Anda pertahankan .

Jika Anda benar-benar menginginkan grup yang ingin Anda ganti, yang mungkin Anda inginkan adalah mesin templat (mis. Kumis, ejs, StringTemplate, ...).


Sebagai tambahan bagi yang penasaran, bahkan grup non-capturing dalam regex hanya ada untuk kasus di mana mesin regex membutuhkan mereka untuk mengenali dan melewati teks variabel. Misalnya, dalam

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

Anda memerlukannya jika masukan Anda dapat berupa " abcabc capture me bcdbcd" atau "abc capture me bcd" atau bahkan "capture me".

Atau dengan kata lain: jika teksnya selalu sama, dan Anda tidak menangkapnya, tidak ada alasan untuk menggunakan grup sama sekali.

Yaro
sumber
1
Kelompok non-penangkap tidak diperlukan; \d(.*)\dsudah cukup.
shmosel
1
Saya tidak mengerti di $11sini. Mengapa 11?
Alexis
1
@Alexis - Ini adalah kekhasan java regex: jika grup 11 belum disetel, java menafsirkan $ 11 sebagai $ 1 diikuti oleh 1.
Yaro
9

Tambahkan grup ketiga dengan menambahkan tanda kurung di sekitarnya .*, lalu ganti urutannya dengan "number" + m.group(2) + "1". misalnya:

String output = m.replaceFirst("number" + m.group(2) + "1");
mkb
sumber
4
Sebenarnya, Matcher mendukung gaya referensi $ 2, jadi m.replaceFirst ("number $ 21") akan melakukan hal yang sama.
Michael Myers
Sebenarnya, mereka tidak melakukan hal yang sama. "number$21"bekerja dan "number" + m.group(2) + "1"tidak.
Alan Moore
2
Sepertinya number$21akan menggantikan grup 21, bukan grup 2 + string "1".
Fernando M. Pinheiro
Ini adalah penggabungan string biasa, bukan? mengapa kita perlu memanggil replaceFirst sama sekali?
Zxcv Mnb
2

Anda bisa menggunakan metode matcher.start () dan matcher.end () untuk mendapatkan posisi grup. Jadi dengan menggunakan posisi ini Anda dapat dengan mudah mengganti teks apa pun.

ydanneg
sumber
2

ganti bidang kata sandi dari 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, "**"));
  }
tingkah
sumber
0

Berikut adalah solusi berbeda, yang juga memungkinkan penggantian satu grup dalam beberapa pertandingan. Ini menggunakan tumpukan untuk membalikkan urutan eksekusi, sehingga operasi string dapat dijalankan dengan aman.

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
sumber