규식이는 가까이하지 말자!
간단한 정규식을 잘 사용하다가, 가끔 전방, 후방, 부정형 등으로 불리는 보고 싶지 않은 정규식을 사용해야 될 때가 있다.
seokDaejin
위와 같은 문장이 있다 생각하자. 여기에 아래와 같은 정규식을 적용해보자.
.(?=Dae)
그럼 매치되는 곳이 Dae앞의 문자인 k에 매칭이 된다. 보통 전방 탐색 설명이 여기서 끝나는 경우가 많은데, 이름부터 바꿔야 한다. 전방 탐색이라니.....
우리는 (?=Dae)가 정확히 어디를 가리키는지 알아야 한다. 어디일까? 바로
seok | Daejin
위에 |로 표시한 부분이다. 따라서 (?=Dae).로 매치하면 D가 매치된다. 즉 전방(lookhead)은?= 뒤에 나오는 단어의 바로 앞을 말한다. 정리하면 아래와 같다.
.(?=Dae) ------------ seokDaejin
(?=Dae). ------------ seokDaejin
전방을 이해하면 후방은 간단한다. ?<= 뒤에 나오는 단어 뒤를 말한다. (?<=Dae)
seokDae | jin
.(?<=Dae) ------------ seokDaejin
(?<=Dae). ------------ seokDaejin
누가 단어를 전방, 후방 했는지 모르지만, 어디를 가리키는 방향이 아니라 지정한 단어의 앞, 뒤라고 이해면 간단하다.
정방, 후방이 정리가 되었으며, 부정형을 해보자. 부정형은 전방, 후방에 있는 =대신 !(느낌표)로 바꿔주면 된다.
.(?!Dae)
.(?<!Dae)
결과는 위에서 붉은색으로 표시한 부분 반대로 된다. 단 주의해야 할 점은 현재 매칭이 하나의 문자를 나타내므로 ! 붙히기 전의 하나의 문자를 빼고 모든 문자들이 각각 매칭 된다는 점이다. 즉 .(?!Dae)의 경우 부정형이 아닐 때 매칭 된 k를 빼고 아래와 같이 모두 각각 매칭 된다.
s, e, o, D, a, e, j, i, n
이제 응용을 하나 해보자. 아래에서 Dae가 포함되지 않은 chojin를 선택하는 정규식은?
seokDaejin
kimDaejin
chojin
'포함되지 않은'이므로 (?!Dae)로 시작하자
그리고 (?!Dae).를 하면 D를 제외한 모든 문자들이 각각 매칭 된다.
seokDaejin
kimDaejin
chojin
앞서 말했지만 여기서 주의해야 될 점은 각각 매칭이 된다는 점이다. 각각 매칭을 원하지 않으므로
((?!Dae).)* 해주면 보기는 비슷하지만 Dae의 D만 빼고 *에 의해 seok, aejin가 각각 한 덩어리로 매칭 된다.
이 정규식에 ^를 붙여주면 D의 뒤에서 매칭된 aejin 제거된다.
$까지 붙여주면 seok, kim이 제거되고 우리가 원하는 chojin만 남게 된다.
^((?!Dae).)*$
여기까지 하면 정답이지만, 조금 더 생각해서 성능까지 올려보자. 정규식에서는 ()를 사용하게 되면 mark 되어 참조 가능한 상태를 유지하기 위해 메모리를 사용한다. (?: 를 붙혀주면 mark하지 않는다. 따라서 아래와 같이 개선하면 조금 성능상 이득을 볼 수 있다.
^(?:(?!Dae).)*$
보통 위 부정형 정규식은 성능이 나쁘므로 소스코드에서 해결하는 것이 더 낳다. 하지만 반드시 정규식으로 해결해야 된다면 위 정규식을 그냥 외워놓고 쓰는게 정신건강에 좋다.
끝.