Program Tip

{m} {n} ( '정확히 n 번'두 번)은 어떻게 작동하나요?

programtip 2020. 10. 13. 19:02
반응형

{m} {n} ( '정확히 n 번'두 번)은 어떻게 작동하나요?


그래서, 어떤 식 으로든 (놀면서) 나는 \d{1}{2}.

논리적으로 나에게는 다음을 의미해야합니다.

(정확히 한 숫자) 정확히 두 번, 즉 정확히 두 번의 숫자.

그러나 실제로는 "정확히 한 번 숫자"를 의미하는 것처럼 보입니다 (따라서를 무시 함 {2}).

String regex = "^\\d{1}{2}$"; // ^$ to make those not familiar with 'matches' happy
System.out.println("1".matches(regex)); // true
System.out.println("12".matches(regex)); // false

유사한 결과는 {n}{m,n}또는 유사한 것을 사용하여 볼 수 있습니다 .

왜 이런 일이 발생합니까? regex / Java 문서 어딘가에 명시 적으로 명시되어 있습니까? 아니면 Java 개발자가 즉석에서 내린 결정입니까, 아니면 버그입니까?

아니면 실제로 무시되지 않고 실제로 완전히 다른 것을 의미합니까?

그다지 중요하지는 않지만 전반적인 정규식 동작은 아닙니다 . Rubular 는 내가 기대하는 바를 수행합니다.

참고-제목은 주로 작동 방식을 알고 싶은 사용자를위한 검색 가능성을위한 것입니다 (이유가 아님).


Java 정규식 구문을 사용하여 RegexBuddy에 정규식을 입력하면 다음 메시지가 표시됩니다.

수량 자 앞에«{2}»를 반복 할 수있는 토큰이 와야합니다.

그룹화를 명시 적으로 사용하도록 정규식을 변경하면 ^(\d{1}){2}해당 오류가 해결되고 예상대로 작동합니다.


Java 정규식 엔진이 단순히 오류 / 표현식을 무시하고 지금까지 컴파일 된 내용으로 작동한다고 가정합니다.

편집하다

@ piet.t의 답변 에서 IEEE-Standard대한 참조 는 그 가정을 뒷받침하는 것 같습니다.

편집 2 (@fncomp에 대한 kudos)

완전성 (?:)을 위해 일반적으로 그룹 캡처를 피하는 데 사용 합니다. 완전한 정규식은 다음이됩니다.^(?:\d{1}){2}


IEEE-Standard 1003.1 은 다음과 같이 말합니다.

인접한 여러 중복 기호 ( '*'및 간격)의 동작은 정의되지 않은 결과를 생성합니다.

따라서 모든 구현은 원하는대로 수행 할 수 있습니다. 특정 항목에 의존하지 마세요.


과학적 접근 :
패턴을 클릭하여 regexplanet.com에서 예제를보고 녹색 Java 버튼을 클릭합니다 .

  • 이미 \d{1}{2}일치 "1"항목을 표시 했지만 일치하지 않습니다 "12". 따라서 로 해석되지 않는다는 것을 알고 있습니다 (?:\d{1}){2}.
  • 그래도 1은 지루한 숫자이며 최적화 {1} 될 수 있습니다
    \d{2}{3}. 더 흥미로운 것을 시도해 보겠습니다 .. 이것은 여전히 ​​두 문자 (6이 아님)와 일치하며 {3}무시됩니다.
  • 확인. 정규식 엔진이하는 일을 쉽게 볼 수있는 방법이 있습니다. 캡처합니까?
    시도해 보겠습니다 (\d{1})({2}). 이상하게도 작동합니다. 두 번째 그룹 $2은 빈 문자열을 캡처합니다.
  • 그렇다면 첫 번째 그룹이 필요한 이유는 무엇입니까? 어때요 ({1})? 여전히 작동합니다.
  • 그리고 그냥 {1}? 거기에 문제 없습니다.
    Java가 여기에서 조금 이상해 보입니다.
  • 큰! 그래서 {1}유효합니다. 우리가 알고있는 자바 확장 *+{0,0x7FFFFFFF}{1,0x7FFFFFFF} 너무 것, *또는 +사용할 수 있습니까? 아니:

    인덱스 0 근처에 매달려있는 메타 문자 '+'
    +
    ^

    유효성 검사는 앞에 와야 *하며 +확장됩니다.

나는 그것이라고 설명 사양에서 아무것도 찾지 못했습니다 보이는 한정사가 문자, 괄호 또는 괄호 후 적어도 와야 같이합니다.

이러한 패턴의 대부분은 다른 정규식 버전에 의해 유효하지 않은 것으로 간주되며 합당한 이유가 있습니다.


처음에 나는 이것이 PatternSyntaxException.

사실에 근거하여 답할 수는 없으므로 이것은 교육을받은 추측 일뿐입니다.

"\\d{1}"    // matches a single digit
"\\d{1}{2}" // matches a single digit followed by two empty strings

나는 {m}{n}어디서도 구문을 본 적이 없습니다 . 이 Rubular 페이지의 정규식 엔진은 그 {2}전에 가능한 가장 작은 토큰에 수량자를 적용하는 것 같습니다 \\d{1}. 이것을 자바 (또는 대부분의 다른 정규식 엔진처럼 보인다)에서 모방하려면 다음 \\d{1}과 같이 그룹화해야합니다 .

^(\\d{1}){2}$

를 참조하십시오 행동에 여기 .


정규식의 컴파일 된 구조

Kobi의 대답 은 경우에 대한 Java regex (Sun / Oracle 구현)의 동작 "^\\d{1}{2}$"또는 "{1}".

다음은 내부적으로 컴파일 된 구조입니다 "^\\d{1}{2}$".

^\d{1}{2}$
Begin. \A or default ^
Curly. Greedy quantifier {1,1}
  Ctype. POSIX (US-ASCII): DIGIT
  Node. Accept match
Curly. Greedy quantifier {2,2}
  Slice. (length=0)

  Node. Accept match
Dollar(multiline=false). \Z or default $
java.util.regex.Pattern$LastNode
Node. Accept match

소스 코드보기

내 조사에서 버그는 아마도 {private 메서드에서 제대로 확인되지 않은 사실 때문일 것입니다 sequence().

이 메서드 sequence()는를 호출 atom()하여 원자를 구문 분석 한 다음를 호출하여 원자에 수량자를 연결 closure()하고 모든 원자를 하나의 시퀀스로 연결합니다.

예를 들어, 다음 정규식이 주어지면 :

^\d{4}a(bc|gh)+d*$

그런 다음 최상위 호출하기 sequence()위해 컴파일 된 노드 받게됩니다 ^, \d{4}, a, (bc|gh)+, d*, $함께 체인을.

이 아이디어를 염두에두고 OpenJDK 8-b132sequence() 에서 복사 한의 소스 코드를 살펴 보겠습니다 (Oracle은 동일한 코드베이스를 사용함).

@SuppressWarnings("fallthrough")
/**
 * Parsing of sequences between alternations.
 */
private Node sequence(Node end) {
    Node head = null;
    Node tail = null;
    Node node = null;
LOOP:
    for (;;) {
        int ch = peek();
        switch (ch) {
        case '(':
            // Because group handles its own closure,
            // we need to treat it differently
            node = group0();
            // Check for comment or flag group
            if (node == null)
                continue;
            if (head == null)
                head = node;
            else
                tail.next = node;
            // Double return: Tail was returned in root
            tail = root;
            continue;
        case '[':
            node = clazz(true);
            break;
        case '\\':
            ch = nextEscaped();
            if (ch == 'p' || ch == 'P') {
                boolean oneLetter = true;
                boolean comp = (ch == 'P');
                ch = next(); // Consume { if present
                if (ch != '{') {
                    unread();
                } else {
                    oneLetter = false;
                }
                node = family(oneLetter, comp);
            } else {
                unread();
                node = atom();
            }
            break;
        case '^':
            next();
            if (has(MULTILINE)) {
                if (has(UNIX_LINES))
                    node = new UnixCaret();
                else
                    node = new Caret();
            } else {
                node = new Begin();
            }
            break;
        case '$':
            next();
            if (has(UNIX_LINES))
                node = new UnixDollar(has(MULTILINE));
            else
                node = new Dollar(has(MULTILINE));
            break;
        case '.':
            next();
            if (has(DOTALL)) {
                node = new All();
            } else {
                if (has(UNIX_LINES))
                    node = new UnixDot();
                else {
                    node = new Dot();
                }
            }
            break;
        case '|':
        case ')':
            break LOOP;
        case ']': // Now interpreting dangling ] and } as literals
        case '}':
            node = atom();
            break;
        case '?':
        case '*':
        case '+':
            next();
            throw error("Dangling meta character '" + ((char)ch) + "'");
        case 0:
            if (cursor >= patternLength) {
                break LOOP;
            }
            // Fall through
        default:
            node = atom();
            break;
        }

        node = closure(node);

        if (head == null) {
            head = tail = node;
        } else {
            tail.next = node;
            tail = node;
        }
    }
    if (head == null) {
        return end;
    }
    tail.next = end;
    root = tail;      //double return
    return head;
}

줄을 기록해 둡니다 throw error("Dangling meta character '" + ((char)ch) + "'");. 이 오류가있는 경우 발생합니다 어디에 +, *, ?매달려하고 선행하는 토큰의 일부가 아닙니다. 보시다시피 {오류를 던지는 경우가 아닙니다. 실제로의 케이스 목록에는 없으며 sequence()컴파일 프로세스는 default케이스별로 직접 atom().

@SuppressWarnings("fallthrough")
/**
 * Parse and add a new Single or Slice.
 */
private Node atom() {
    int first = 0;
    int prev = -1;
    boolean hasSupplementary = false;
    int ch = peek();
    for (;;) {
        switch (ch) {
        case '*':
        case '+':
        case '?':
        case '{':
            if (first > 1) {
                cursor = prev;    // Unwind one character
                first--;
            }
            break;
        // Irrelevant cases omitted
        // [...]
        }
        break;
    }
    if (first == 1) {
        return newSingle(buffer[0]);
    } else {
        return newSlice(buffer, first, hasSupplementary);
    }
}

프로세스가으로 들어가면 즉시 atom()만나기 {때문에 끊어 switch지고 for반복 되며 길이가 0 인 새 슬라이스 가 생성됩니다 (길이는에서 시작 first, 즉 0).

When this slice is returned, the quantifier is parsed by closure(), resulting in what we see.

Comparing the source code of Java 1.4.0, Java 5 and Java 8, there doesn't seem to be much changes in the source code of sequence() and atom(). It seems this bug has been there since the beginning.

Standard for regular expression

The top-voted answer citing IEEE-Standard 1003.1 (or POSIX standard) is irrelevant to the discussion, since Java does not implement BRE and ERE.

There are many syntax resulting in undefined behavior according to the standard, but is well-defined behavior across many other regex flavors (though whether they agree or not is another matter). For example, \d is undefined according to the standard, but it matches digits (ASCII/Unicode) in many regex flavors.

Sadly, there is no other standard on regular expression syntax.

There is, however, a standard on Unicode Regular Expression, which focuses on features a Unicode regex engine should have. Java Pattern class more or less implements Level 1 support as described in UTS #18: Unicode Regular Expression and RL2.1 (albeit extremely buggy).


I am guessing that in definition of {} is something like "look back to find valid expression (excluding myself - {}", so in your example there is nothing between } and {.

Anyway, if you wrap it in parenthesis it will work as you expected: http://refiddle.com/gv6.

참고URL : https://stackoverflow.com/questions/18958747/how-does-mn-exactly-n-times-twice-work

반응형