화요정규식 – 1주차 : 반복단어 강조

우연히 Regex Tuesday라는 재미있는 사이트를 하나 발견했습니다. 매주 화요일에 정규식 문제를 올려두고 이를 바로 확인해 볼 수 있도록 만들어진 사이트입니다.

평소에 정규식에 관심도 많고 문제를 푸는 것도 재미있고 해서 시간나는 대로 이 사이트에 나온 문제에 대한 제 답과 해설을 달아볼 생각입니다. 순전히 제가 재미있자고 하는 일이지만 정규표현식을 공부하는 분들에게도 도움이 되었으면 하는 바람입니다.

1주차 문제는 다음과 같습니다. 왼쪽에 있는 문자열에 정규표현식 치환을 사용하여 오른쪽에 있는 결과물처럼 만믈면 됩니다.

This is a test This is a test
This is is a test This is <strong>is</strong> a test
This test test is is This test <strong>test</strong> is <strong>is</strong>
This test is a test This test is a test
This this test is a test This <strong>this</strong> test is a test
cat dog dog cat dog cat dog <strong>dog</strong> cat dog
This test is a test tester This test is a test tester
hello world hello world hello world hello world
This nottest test is something This nottest test is something
This is IS a test This is <strong>IS</strong> a test
<Mack> I’ll I’ll be be back back in in a a bit bit. <Mack> I’ll <strong>I’ll</strong> be <strong>be</strong> back <strong>back</strong> in <strong>in</strong> a <strong>a</strong> bit <strong>bit</strong>.

해당 사이트로 가면 정규식을 바로 작성해서 정답을 확인해 볼 수 있으므로 꼭 먼저 직접 풀어보시길 바랍니다. 당연한 얘기지만 제가 제시하는 답 외에도 다른 답이 존재할 수도 있으니, 어디까지나 참고용으로만 보셨으면 합니다. :)

[toggle txt_show=”1주차 풀이 보기” txt_hide=”1주차 풀이 감추기”]해답
정규식은 /\b([\w']+) (\1)\b/gi
치환식은 $1 <strong>$2</strong>

풀이
먼저 반복되는 단어를 찾아야 합니다.

단어 하나를 찾을 때는 흔히 \w+를 사용하는데 \w는 영문자 A-Z, a-z 숫자 0-9 특수문자 _를 포함하는 이스케이프 문자입니다. 따라서 \w+라고 입력하면 사실은 [A-Za-z0-9_]+를 입력한 것과 같은 뜻입니다. 문자 뒤의 +는 1개 또는 그 이상을 의미하는 수량자(quantifier)이므로 \w+은 “모든 문자가 A부터 Z까지 또는 a부터 z까지 또는 0부터 9까지 또는 _로 이루어진 연속되는 문자열”에 매칭됩니다. 예를 들어 “This is a test”라는 문장을 \w+로 찾아보면 “This”, “is”, “a”, “test”가 매칭된다고 나옵니다. 그런데 마지막 패턴을 보면 I'll도 반복되는 단어에 포함시켰습니다. 작은따옴표는 \w에 포함되어 있지 않으므로 따로 패턴을 추가해주어야 합니다. 그래서 최종적으로 한 단어에 매칭하는 정규표현식은 [\w']+가 되었습니다.
저는 이와 같이 단어 패턴을 만들었지만 만약 이 문제에만 딱 맞는 답을 원한다면 \S+를 사용할 수도 있습니다(주의! 대문자 S입니다). \S는 ‘공백이 아닌’ 문자에 매칭하는 이스케이프 문자입니다. 이 문제에는 공백 또는 단어 밖에 없으므로 \S로 단어를 매칭시킬 수 있습니다.

단어를 찾았으면 그 다음에는 이 단어가 반복되는 패턴을 만듭니다. 일단 단어 패턴을 괄호로 감쌉니다.
([\w']+)
괄호로 감싼 부분은 서브 패턴(sub pattern)이라고 부르는데 정규식을 실행하고 난 다음에 매칭된 부분을 찾을 때 편리합니다. 예를 들어, 자바스크립트에서 /([0-9]+)px/.test("The height is 300px") 와 같이 정규식을 실행하면 첫 번째 매칭된 부분을 저장하는 RegExp.$1에는 [0-9]+에 해당하는 값, 즉 “300”이 저장됩니다.
서브 패턴의 번호는 여는 괄호가 나온 순서대로 매겨집니다. 앞의 예에서 정규식을 /(([0-9]+)px)/ 로 바꾸면 괄호가 열린 순서에 의해 RegExp.$1에는 “300px”가, RegExp.$2에는 “300”이 저장됩니다. 조금 긴 정규식을 작성하다보면 간혹 괄호의 짝을 잘 못 맞추거나 순서를 헷갈리는 경우가 있으니 주의하셔야 합니다.
서브 패턴은 한 정규식 내에서도 사용할 수 있습니다. 예를 들어 첫 번째 패턴을 다시 불러오고 싶다면 \1과 같이 사용합니다. 백 레퍼런스(back reference)라 부르는 이러한 문법은 반복되는 문자열을 찾을 때 좋습니다. 바로 지금 우리가 사용할 것이죠. 이제 찾은 단어를 한 번 더 반복하는 패턴을 다음과 같이 검색할 수 있습니다. 단어와 단어 사이에는 한 칸의 공백이 있으므로 단어 문법과 백 레퍼런스 사이에도 공백을 하나 두겠습니다.
/([\w']+) \1/
나중에 치환할 때 사용하기 위해 백 레퍼런스도 서브 패턴으로 만들도록 하겠습니다.
/([\w']+) (\1)/

그런데 여기까지 해서 매칭되는 부분을 찾아보면 “This is a test” 중 “is is”에 매칭이 된다는 것을 알 수 있습니다.
"This is a test".match(/([\w']+) (\1)/)
첫 번째 서브 패턴은 한 단어의 일부가 아닌 전체에 매칭되어야 하므로 [\w']+ 앞에는 단어의 경계(\b)가 있도록 만들겠습니다. 단어 경계는 단어가 아닌 부분에 매칭되는 이스케이프 문자입니다. 단, 매칭 결과에서는 나타나지 않습니다. 예를 들어, "moon".match(/\bm/)을 실행하면 “moon”에 있는 “m”에 매칭이 되지만, "moon".match(/\bo/)를 실행하면 아무 것에도 매칭이 되지 않습니다. 단어 경계까지 포함시킨 패턴은 다음과 같습니다.
/\b([\w']+) (\1)\b/

다음으로는 변경자(modifier)를 추가하겠습니다. 변경자는 작성된 정규식 패턴의 성격을 바꾸어주는데, 자바스크립트 정규식에서는 닫는 슬래시(/) 뒤에 추가합니다. 우리가 추가할 변경자는 g(global, 전역 매칭)와 i(ignore case, 대소문자 구분안함) 입니다. 전역 매칭을 사용하지 않으면 정규식은 패턴에 일치하는 첫 번째 문자열만 찾고 종료되지만, 전역 매칭을 사용하면 패턴에 일치하는 모든 문자열을 찾습니다(표 3번째 문자열 참고). 대소문자 구분은 말 그대로 패턴 내에서 대소문자를 구분하지 않도록 합니다. 이 변경자를 사용해야 “This”를 참조한 백 레퍼런스가 “this”에도 매칭이 됩니다(표 5번째 문자열).

이제 매칭을 끝냈으면 치환할 차례입니다. 치환은 간단합니다. 치환 문자열 안에서 $1는 첫 번째 서브 패턴의 매칭 문자열로, $2는 두 번째 서브 패턴의 매칭 문자열로 바뀐다는 점만 기억하면 원하는 결과를 다음과 같이 얻을 수 있습니다.
$1 <strong>$2</strong>
예를 들어, “This this is a test”에서 $1는 “This”, $2는 “this”가 됩니다.
[/toggle]

Leave a Reply