[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>)강력한 기능 입니다. 캡처 를 정확하게 제어 할 수 있습니다. 패턴에서 더 많은 것을 얻으려고 할 때 명심하십시오.


답변