[c#] 정규식 균형 그룹이란 무엇입니까?
나는 이중 중괄호 ( 이 질문 ) 안에 데이터를 가져 오는 방법에 대한 질문을 읽은 다음 누군가 균형 그룹을 가져 왔습니다. 나는 그들이 무엇인지, 어떻게 사용하는지 여전히 잘 모르겠습니다.
Balancing Group Definition을 읽었 지만 설명을 따르기가 어렵고 제가 언급 한 질문에 대해 여전히 혼란 스럽습니다.
누군가 밸런싱 그룹이 무엇이며 어떻게 유용한 지 설명 할 수 있습니까?
답변
내가 아는 한, 밸런싱 그룹은 .NET의 정규식 특성에 고유합니다.
곁에 : 반복 된 그룹
먼저, .NET이 단일 캡처 링 그룹의 여러 캡처에 액세스 할 수있는 유일한 정규식 버전이라는 것을 알아야합니다 (역 참조가 아니라 일치가 완료된 후).
예를 들어이를 설명하기 위해
(.)+
및 문자열 "abcd"
.
다른 모든 정규식 맛, 그룹을 캡처하는 것은 1
단순히 하나 개의 결과를 얻을 것입니다 : d
(주 전체 경기는 물론됩니다 abcd
예상대로). 캡처 그룹을 새로 사용할 때마다 이전 캡처를 덮어 쓰기 때문입니다.
반면에 .NET은 이들을 모두 기억합니다. 그리고 그것은 스택에서 그렇게합니다. 위의 정규식을 다음과 같이 일치시킨 후
Match m = new Regex(@"(.)+").Match("abcd");
당신은 그것을 찾을 것입니다
m.Groups[1].Captures
A는 CaptureCollection
해당 요소의 대응에 네 캡처
0: "a"
1: "b"
2: "c"
3: "d"
여기서 숫자는 CaptureCollection
. 따라서 기본적으로 그룹이 다시 사용될 때마다 새로운 캡처가 스택으로 푸시됩니다.
명명 된 캡처 그룹을 사용하면 더 흥미로워집니다. .NET은 동일한 이름의 반복 사용을 허용하기 때문에 다음과 같은 정규식을 작성할 수 있습니다.
(?<word>\w+)\W+(?<word>\w+)
두 단어를 같은 그룹으로 캡처합니다. 다시 말하지만, 특정 이름을 가진 그룹이 발견 될 때마다 캡처가 스택으로 푸시됩니다. 따라서이 정규식을 입력에 적용 "foo bar"
하고
m.Groups["word"].Captures
우리는 두 개의 캡처를 찾습니다
0: "foo"
1: "bar"
이를 통해 표현식의 다른 부분에서 단일 스택으로 항목을 푸시 할 수도 있습니다. 그러나 여전히 이것은 .NET에 나열된 여러 캡처를 추적 할 수있는 .NET의 기능 일뿐 CaptureCollection
입니다. 하지만이 컬렉션은 스택 입니다. 그래서 우리가 할 수 팝업 그것에서 일을?
입력 : 균형 그룹
우리가 할 수 있다는 것이 밝혀졌습니다. 와 같은 그룹을 사용하는 경우 하위 표현식이 일치 (?<-word>...)
하면 스택에서 마지막 캡처가 팝됩니다 . 따라서 이전 표현을 다음과 같이 변경하면word
...
(?<word>\w+)\W+(?<-word>\w+)
그런 다음 두 번째 그룹은 첫 번째 그룹의 캡처 CaptureCollection
를 표시하고 결국 빈 메시지 를 받게됩니다 . 물론이 예제는 쓸모가 없습니다.
그러나 마이너스 구문에 대한 세부 정보가 하나 더 있습니다. 스택이 이미 비어 있으면 그룹이 실패합니다 (하위 패턴에 관계없이). 이 동작을 활용하여 중첩 수준을 계산할 수 있습니다. 여기에서 이름 균형 그룹의 출처 (그리고 흥미로운 곳)입니다. 올바르게 괄호로 묶인 문자열을 일치시키고 싶다고 가정 해 보겠습니다. 스택의 각 여는 괄호를 밀고 닫는 괄호마다 하나의 캡처를 팝합니다. 닫는 괄호가 너무 많이 발생하면 빈 스택을 팝하고 패턴이 실패하게됩니다.
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$
그래서 우리는 반복에서 세 가지 대안을 가지고 있습니다. 첫 번째 대안은 괄호가 아닌 모든 것을 소비합니다. 두 번째 대안은 (
s를 스택에 밀어 넣는 동안 일치 시킵니다. 세 번째 대안 )
은 스택에서 요소를 꺼내는 동안 s 와 일치 합니다 (가능한 경우!).
참고 : 명확히하기 위해 일치하지 않는 괄호가 없는지 확인하고 있습니다. 전혀 괄호를 포함하지 않는 문자열이 있음이 의미 합니다 그들은 (당신이 경기에 괄호를 할 몇 가지 구문) 여전히 구문 적으로 유효하기 때문에, 일치합니다. 하나 이상의 괄호 세트를 확인 (?=.*[(])
하려면 ^
.
이 패턴은 완벽하지 않거나 완전히 정확하지 않습니다.
피날레 : 조건부 패턴
또 하나의 캐치가 있습니다. 이것은 스택이 문자열의 끝에서 비어 있음을 보장하지 않습니다 (따라서 (foo(bar)
유효 할 것입니다). .NET (및 기타 여러 버전)에는 여기에 도움이되는 하나의 추가 구조 인 조건부 패턴이 있습니다. 일반적인 구문은 다음과 같습니다.
(?(condition)truePattern|falsePattern)
여기서는 falsePattern
선택 사항입니다. 생략하면 거짓 대소 문자가 항상 일치합니다. 조건은 패턴이거나 캡처 그룹의 이름 일 수 있습니다. 여기서는 후자의 경우에 초점을 맞출 것입니다. 캡처 그룹의 이름 인 truePattern
경우 해당 특정 그룹의 캡처 스택이 비어 있지 않은 경우에만 사용됩니다. 즉, (?(name)yes|no)
” name
(아직 스택에있는) 무언가를 일치시키고 캡처 yes
한 경우 패턴을 사용하고 그렇지 않으면 패턴을 사용하십시오 “라는 읽기 와 같은 조건부 패턴 no
입니다.
따라서 위의 패턴 끝에 -stack이 비어 있지 않은 (?(Open)failPattern)
경우 전체 패턴이 실패하는 것과 같은 것을 추가 할 수 있습니다 Open
. 패턴을 무조건 실패하게 만드는 가장 간단한 방법은 (?!)
(빈 부정적 예측)입니다. 따라서 최종 패턴이 있습니다.
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$
이 조건부 구문은 균형 그룹과는 그 자체로 관련이 없지만 전체 기능을 활용해야합니다.
여기에서 하늘이 한계입니다. 많은 매우 정교한 사용이 가능하며 가변 길이 lookbehinds와 같은 다른 .NET-Regex 기능과 함께 사용하면 몇 가지 문제가 발생합니다 ( 내가 어려운 방법을 배워야했습니다 ). 그러나 주요 질문은 항상 다음과 같습니다. 이러한 기능을 사용할 때 코드를 계속 유지할 수 있습니까? 정말 잘 문서화하고 작업하는 모든 사람이 이러한 기능을 알고 있는지 확인해야합니다. 그렇지 않으면 문자열을 문자별로 수동으로 걷고 정수의 중첩 수준을 계산하는 것이 더 나을 수 있습니다.
부록 : (?<A-B>...)
구문 은 무엇입니까 ?
이 부분에 대한 크레딧은 Kobi로 이동합니다 (자세한 내용은 아래 답변 참조).
이제 위의 모든 것을 사용하여 문자열이 올바르게 괄호로 묶여 있는지 확인할 수 있습니다. 그러나 모든 괄호의 내용에 대해 실제로 (중첩 된) 캡처를 얻을 수 있다면 훨씬 더 유용 할 것입니다. 물론 비워지지 않은 별도의 캡처 스택에서 여는 괄호와 닫는 괄호를 기억 한 다음 별도의 단계에서 위치에 따라 일부 하위 문자열 추출을 수행 할 수 있습니다.
그러나 .NET은 여기에 한 가지 더 편리한 기능을 제공합니다.를 사용하면 (?<A-B>subPattern)
스택에서 캡처 된 캡처 일뿐만 아니라 B
해당 캡처 된 캡처 B
와이 현재 그룹 사이의 모든 항목 이 스택에 푸시 A
됩니다. 따라서 닫는 괄호에 이와 같은 그룹을 사용하고 스택에서 중첩 수준을 표시하는 동안 쌍의 내용을 다른 스택으로 푸시 할 수도 있습니다.
^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$
Kobi 는 그의 답변 에서이 라이브 데모 를 제공했습니다.
따라서이 모든 것을 종합하면 다음을 수행 할 수 있습니다.
- 임의로 많은 캡처를 기억
- 중첩 된 구조 확인
- 각 중첩 수준 캡처
모두 하나의 정규식입니다. 흥미롭지 않다면 …;)
내가 처음 배웠을 때 도움이 된 몇 가지 리소스 :
답변
M. Buettner의 탁월한 답변에 약간의 추가 사항 :
와 거래 무엇 (?<A-B>)
구문은?
(?<A-B>x)
와 미묘하게 다릅니다 (?<-A>(?<B>x))
. 결과적으로 동일한 제어 흐름 * 을 생성하지만 다르게 캡처 합니다.
예를 들어 균형 잡힌 중괄호의 패턴을 살펴 보겠습니다.
(?:[^{}]|(?<B>{)|(?<-B>}))+(?(B)(?!))
경기가 끝나면 우리는 균형 잡힌 문자열을 가지지 만 그게 전부 입니다. 스택이 비어 있기 때문에 중괄호가 어디에 있는지 알 수 없습니다 B
. 엔진이 우리를 위해했던 노력은 사라졌습니다.
( Regex Storm의 예 )
(?<A-B>x)
그 문제에 대한 해결책입니다. 어떻게? 그것은 하지 않습니다 캡처 x
로 $A
는 이전 캡처 사이의 내용 캡처 : B
현재 위치를.
패턴에서 사용합시다.
(?:[^{}]|(?<Open>{)|(?<Content-Open>}))+(?(Open)(?!))
이것은 $Content
길을 따라 각 쌍에 대해 중괄호 (및 그 위치) 사이의 문자열 로 캡처됩니다 .
문자열이 들어 {1 2 {3} {4 5 {6}} 7}
있을 것 네 캡처 : 3
, 6
, 4 5 {6}
,와 1 2 {3} {4 5 {6}} 7
– 훨씬 더 이상 아무것도 나 }
}
}
}
.
( 예- table
탭을 클릭하고 ${Content}
, 캡처를보십시오 )
사실, 그것은 전혀 균형을 맞추지 않고 사용할 수 있습니다 (?<A>).(.(?<Content-A>).)
. 그룹으로 분리되어 있더라도 처음 두 문자를 캡처합니다.
(예측은 여기에서 더 일반적으로 사용되지만 항상 확장되는 것은 아닙니다. 논리를 복제 할 수 있습니다.)
(?<A-B>)
강력한 기능 입니다. 캡처 를 정확하게 제어 할 수 있습니다. 패턴에서 더 많은 것을 얻으려고 할 때 명심하십시오.