ฉันสามารถแทนที่กลุ่มใน Java regex ได้หรือไม่


106

ฉันมีรหัสนี้และฉันต้องการทราบว่าฉันสามารถแทนที่เฉพาะกลุ่ม (ไม่ใช่รูปแบบทั้งหมด) ใน Java regex ได้หรือไม่ รหัส:

 //...
 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
คุณช่วยชี้แจงคำถามของคุณได้ไหมเช่นอาจให้ผลลัพธ์ที่คาดหวังสำหรับอินพุตนั้น
Michael Myers

คำตอบ:


130

การใช้งาน$n(ที่ n คือหลัก) ในการอ้างถึง subsequences replaceFirst(...)บันทึกใน ฉันสมมติว่าคุณต้องการแทนที่กลุ่มแรกด้วยสตริงตัวอักษร"number"และกลุ่มที่สองด้วยค่าของกลุ่มแรก

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+) เป็นผู้จับคู่โลภและในตอนแรกจะใช้ตัวเลขสุดท้าย จากนั้นผู้จับคู่จะต้องย้อนรอยเมื่อรู้ว่าสุดท้ายไม่มีอะไรที่ตรงกันก่อนที่จะจับคู่กับตัวเลขสุดท้าย(.*)*(\d)


7
คงจะดีไม่น้อยถ้าคุณจะโพสต์ตัวอย่างผลลัพธ์
winklerrr

6
สิ่งนี้ใช้ได้ผลในนัดแรก แต่จะไม่ได้ผลหากมีหลายกลุ่มและคุณกำลังทำซ้ำในช่วงเวลาหนึ่ง (m.find ())
Hugo Zaragoza

1
ฉันเห็นด้วยกับ Hugo นี่เป็นวิธีที่แย่มากในการนำโซลูชันไปใช้ ... เหตุใดสิ่งนี้จึงเป็นคำตอบที่ยอมรับในโลกและไม่ใช่คำตอบของ acdcjunior ซึ่งเป็นทางออกที่สมบูรณ์แบบ: รหัสจำนวนน้อยการเชื่อมต่อกันสูงและการมีเพศสัมพันธ์ต่ำโอกาสน้อยกว่ามาก (ถ้าไม่มีโอกาส) ของผลข้างเคียงที่ไม่ต้องการ ... ถอนหายใจ ...
FireLight

คำตอบนี้ใช้ไม่ได้ในขณะนี้ สิ่งที่m.replaceFirst("number $2$1");ควรจะเป็นm.replaceFirst("number $3$1");
Daniel Eisenreich

58

คุณสามารถใช้Matcher#start(group)และ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, "!!!"));
}

ตรวจสอบออนไลน์สาธิตที่นี่


2
นี่ควรเป็นคำตอบที่ได้รับการยอมรับซึ่งเป็นโซลูชันที่ "พร้อมใช้งาน" ที่สมบูรณ์แบบที่สุดโดยไม่ต้องมีการเชื่อมต่อกับโค้ดประกอบ แม้ว่าฉันจะแนะนำให้เปลี่ยนชื่อวิธีการของหนึ่งในนั้น เมื่อมองแวบแรกดูเหมือนการเรียกซ้ำในวิธีแรก
FireLight

พลาดโอกาสในการแก้ไข นำส่วนที่เกี่ยวกับการเรียกซ้ำกลับมาวิเคราะห์รหัสไม่ถูกต้อง โอเวอร์โหลดทำงานร่วมกันได้ดี
FireLight

โซลูชันแบบสำเร็จรูปนี้เหมาะสำหรับการแทนที่การเกิดครั้งเดียวและกลุ่มเดียวเท่านั้นและเนื่องจากการคัดลอกสตริงเต็มด้วยการแทนที่แต่ละครั้งจึงไม่เหมาะสมอย่างยิ่งสำหรับวัตถุประสงค์อื่นใด แต่มันเป็นจุดเริ่มต้นที่ดี น่าเสียดายที่ Java เป็นเรื่องไร้สาระ แต่ขาดสิ่งอำนวยความสะดวกในการจัดการสตริงขั้นพื้นฐาน
9ilsdx 9rvj 0lo

26

เสียใจที่เอาชนะม้าตาย แต่เป็นเรื่องแปลกที่ไม่มีใครชี้ให้เห็น - "ใช่คุณทำได้ แต่นี่ตรงกันข้ามกับวิธีที่คุณใช้การจับกลุ่มในชีวิตจริง"

หากคุณใช้ Regex ตามที่ควรจะใช้วิธีแก้ปัญหานั้นง่ายดังนี้:

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

หรือตามที่ shmosel ชี้ให้เห็นโดยชอบธรรมด้านล่าง

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

... เนื่องจากใน regex ของคุณไม่มีเหตุผลที่ดีในการจัดกลุ่มทศนิยมเลย

โดยปกติคุณจะไม่ใช้กลุ่มการจับภาพในส่วนของสตริงที่คุณต้องการทิ้งคุณใช้กับส่วนของสตริงที่คุณต้องการเก็บไว้ให้

หากคุณต้องการกลุ่มที่คุณต้องการแทนที่จริงๆสิ่งที่คุณอาจต้องการแทนคือเครื่องมือสร้างเทมเพลต (เช่นหนวด, ejs, StringTemplate, ... )


นอกเหนือจากคนที่อยากรู้อยากเห็นแล้วแม้แต่กลุ่มที่ไม่ได้จับภาพใน regexes ก็มีไว้สำหรับกรณีที่เอนจิน regex ต้องการให้พวกเขาจดจำและข้ามข้อความตัวแปร ตัวอย่างเช่นใน

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

คุณต้องใช้หากข้อมูลของคุณมีลักษณะเหมือน "abcabc capture me bcdbcd" หรือ "abc capture me bcd" หรือแม้แต่ "capture me"

หรือจะวางไว้ในทางกลับกัน: ถ้าข้อความเหมือนกันเสมอและคุณไม่ได้จับมันก็ไม่มีเหตุผลที่จะใช้กลุ่มเลย


1
กลุ่มที่ไม่จับภาพนั้นไม่จำเป็น \d(.*)\dจะพอเพียง
shmosel

2
ฉันไม่เข้าใจที่$11นี่ ทำไม 11?
Alexis

1
@ Alexis - นี่คือมุมมองของ java regex: ถ้ากลุ่ม 11 ไม่ได้รับการตั้งค่า java จะตีความ $ 11 เป็น $ 1 ตามด้วย 1.
Yaro

9

เพิ่มกลุ่มที่สามโดยการเพิ่ม parens รอบ.*แล้วแทนที่ subsequence "number" + m.group(2) + "1"กับ เช่น:

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

4
จริงๆแล้ว Matcher รองรับรูปแบบการอ้างอิง $ 2 ดังนั้น m.replaceFirst ("หมายเลข $ 21") จะทำสิ่งเดียวกัน
Michael Myers

จริงๆแล้วพวกเขาไม่ได้ทำสิ่งเดียวกัน "number$21"ใช้ได้ผลและ"number" + m.group(2) + "1"ไม่ได้ผล
Alan Moore

2
ดูเหมือนว่าnumber$21จะแทนที่กลุ่ม 21 ไม่ใช่กลุ่ม 2 + สตริง "1"
Fernando M.Pinheiro

นี่คือการต่อสตริงธรรมดาใช่ไหม ทำไมเราต้องเรียก replaceFirst เลย?
Zxcv Mnb

2

คุณสามารถใช้ matcher.start () และ matcher.end () เมธอดเพื่อรับตำแหน่งกลุ่ม ดังนั้นการใช้ตำแหน่งนี้คุณสามารถแทนที่ข้อความใด ๆ ได้อย่างง่ายดาย


2

แทนที่ฟิลด์รหัสผ่านจากอินพุต:

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

นี่คือวิธีแก้ปัญหาที่แตกต่างกันซึ่งยังช่วยให้สามารถแทนที่กลุ่มเดียวในการแข่งขันหลายรายการได้ มันใช้สแต็กเพื่อย้อนกลับคำสั่งการดำเนินการดังนั้นการดำเนินการสตริงจึงสามารถดำเนินการได้อย่างปลอดภัย

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();       
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.