Java 정규식에서 그룹을 바꿀 수 있습니까?


95

이 코드가 있는데 Java 정규식에서 그룹 (모든 패턴이 아님) 만 바꿀 수 있는지 알고 싶습니다. 암호:

 //...
 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

답변:


125

$n에서 캡처 된 하위 시퀀스를 참조 하려면 (여기서 n은 숫자)를 사용합니다 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 2015-08-25

6
이 첫 경기에서 작동하지만 실 거예요 작업이 많은 그룹이있는 경우 당신은 잠시 동안 (m.find ())로 이상 반복하는이다
휴고 사라고사

1
나는 Hugo와 동의합니다. 이것은 솔루션을 구현하는 끔찍한 방법입니다 ... 지구상에서 이것이 acdcjunior의 대답이 아닌 받아 들여진 대답 인 이유-완벽한 솔루션입니다 : 적은 양의 코드, 높은 응집력 및 낮은 결합, 훨씬 적은 기회 (그렇지 않으면 기회 없음) 원치 않는 부작용 ...의 한숨 ...
반딧불

이 답변은 현재 유효하지 않습니다. 는 m.replaceFirst("number $2$1");해야한다m.replaceFirst("number $3$1");
다니엘 Eisenreich

52

당신은 사용할 수 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, "!!!"));
}

여기에서 온라인 데모를 확인 하십시오 .


1
이것은 수반되는 코드에 대한 결합 수준을 도입하지 않고도 가장 완전하고 "바로 사용할 수있는"솔루션 인 허용되는 대답이어야합니다. 그중 하나의 메서드 이름을 변경하는 것이 좋습니다. 언뜻보기에는 첫 번째 메서드의 재귀 호출처럼 보입니다.
FireLight

편집 기회를 놓쳤습니다. 재귀 호출에 대한 부분을 되찾고 코드를 제대로 분석하지 않았습니다. 오버로드는 함께 잘 작동
반딧불

23

죽은 말을 이겨서 미안하지만 아무도 이것을 지적하지 않았다는 것은 좀 이상합니다. "예, 할 수 있습니다.하지만 이것은 실제 생활에서 캡처 그룹을 사용하는 방법과 반대입니다."

Regex를 원래 사용되는 방식으로 사용하는 경우 솔루션은 다음과 같이 간단합니다.

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

또는 아래 shmosel이 올바르게 지적했듯이,

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

... 정규식에서 소수를 그룹화 할 이유가 전혀 없기 때문입니다.

일반적으로 삭제 하려는 문자열 부분에 캡처 그룹을 사용하지 않고 유지 하려는 문자열 부분에 사용합니다 .

정말로 바꾸고 싶은 그룹을 원한다면 템플릿 엔진 (예 : moustache, ejs, StringTemplate, ...)을 원할 것입니다.


호기심을 제외하고, 정규식 엔진이 가변 텍스트를 인식하고 건너 뛰기 위해 필요로하는 경우 정규식에서 캡처하지 않는 그룹도 있습니다. 예를 들어

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

입력이 "abcabc capture me bcdbcd"또는 "abc capture me bcd"또는 "capture me" .

또는 반대로 말하면 텍스트가 항상 동일하고 캡처하지 않으면 그룹을 사용할 이유가 전혀 없습니다.


1
캡처하지 않는 그룹은 필요하지 않습니다. \d(.*)\d충분합니다.
shmosel

1
나는 $11여기를 이해하지 못한다 . 왜 11일까요?
Alexis

1
@Alexis-이것은 자바 정규식 특징입니다 : 그룹 11이 설정되지 않은 경우, 자바는 $ 11을 $ 1 다음에 1로 해석합니다.
Yaro

9

주위 .*에 괄호를 추가하여 세 번째 그룹을 추가 한 다음 하위 시퀀스를 "number" + m.group(2) + "1". 예 :

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

4
실제로 Matcher는 $ 2 스타일의 참조를 지원하므로 m.replaceFirst ( "number $ 21")도 동일한 작업을 수행합니다.
Michael Myers

사실 그들은 같은 일 을 하지 않습니다 . "number$21"작동하고 작동 "number" + m.group(2) + "1"하지 않습니다.
Alan Moore

2
number$21그룹 2 + 문자열 "1"이 아닌 그룹 21을 대체하는 것처럼 보입니다 .
Fernando M. Pinheiro

이것은 일반 문자열 연결입니다. 왜 우리는 replaceFirst를 호출해야합니까?
Zxcv Mnb

2

matcher.start () 및 matcher.end () 메서드를 사용하여 그룹 위치를 가져올 수 있습니다. 따라서이 위치를 사용하면 모든 텍스트를 쉽게 바꿀 수 있습니다.


1

입력에서 비밀번호 필드를 바꿉니다.

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

0

다음은 여러 경기에서 단일 그룹을 교체 할 수있는 다른 솔루션입니다. 스택을 사용하여 실행 순서를 반대로하기 때문에 문자열 작업을 안전하게 실행할 수 있습니다.

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.