티스토리 뷰

(3) 한정자 (quantifier)

quantifier의 의미는 수량 한정자입니다. 정규표현식은 반복과 선택(합집합)을 나타낼 수 있는 언어이므로 반복이 매우 중요한 기능입니다. 여기서 한정자는 반복을 몇번 할 것인가, 또는 얼마나 허용할 것인가에 관한 규칙을 표현하게 해 줍니다. 우리가 흔히 알고 있듯이 *는 0번 이상, +는 한번 이상 무한번까지 제한없는 반복을 의미합니다. 프로그래밍 언어의 정규표현식은 그냥 반복을 나타내는 *나 + 이외에도 몇번 반복할 수 있는지 범위를 정확히 표현할 수 있는 기능을 제공합니다.

  • X?   X가 한번 또는 0
  • X*   X0번 이상
  • X+  X가 한번 이상
  • X{n}   X가 정확히 n번 반복, n은 정수
  • X{n,}   Xn번 이상 반복
  • X{n,m}   Xn번 이상, m번 이하

이제 반복이 가능해 짐으로써 우리는 정규표현식을 이용해 아주 복잡하고 긴 패턴도 효과적으로 표현할 수 있게 됩니다. 정규표현식 테스트 프로그램

(1) regex 먼저    (2) input 먼저   (3) 그룹찾기   (기타) 종료.. 1

regex: ab+c+
input: aabbccccdddd
찾은 문자열  "abbcccc" (1, 8)
input: bbbccccdddd
No match found.
input: abcde
찾은 문자열  "abc" (0, 3)
input: aaccdd
No match found.
input: 

위 예제의 패턴은 a가 한번 나온 후 b가 한번 이상, c도 한번 이상 반복되는 입력을 매치하는 규칙을 나타냅니다. 이러한 반복 기능을 문자 부류와 결합하면 상당히 강력한 패턴을 만들 수 있겠지요?

이번에는 입력을 먼저 넣고 다양한 패턴을 검사해보는 두 번째 메뉴를 이용해 봅시다.

긴 입력을 한번 넣어두고 여러 가지 형태의 패턴으로 검사를 할 수 있습니다. 그리고 여기서는 전화번호를 표현하기 위해 {반복회수}를 이용했습니다. \d{3}은 숫자를 세번 반복, 즉 연속된 세 개의 숫자를 나타내는 패턴입니다. 이것이 \d\d\d라고 쓰는 것보다 훨씬 편리하겠지요?

위의 입력에 대해 숫자와 -로 이루어진 부분을 모두 찾으려면 어떻게 해야 할가요? 숫자와 -가 반복된다는 의미는 [\d-]{3}과 같이 쓸 수 있습니다. [...]안에서 \d는 그대로 숫자를 나타내는 메타 심볼의 의미가 유지됩니다. 그럼 우편번호와 상세주소 부분까지 모두 네 개가 찾아지게 되겠지요?

또한 {m, n}은 m번 이상 n번 이하의 반복을 나타냅니다. 이렇게 반복 회수의 범위를 정확히 표시할 수 있는 기능은 실제로 우리가 특정 조건을 기술할 때 상당히 유용하게 쓰입니다.

Enter your regex: a{2}
Enter input string to search: aabaaa
찾은 문자열  "aa" (0, 2)
찾은 문자열  "aa" (3, 5)

Enter your regex: a{2,3}
Enter input string to search: aabaaa
찾은 문자열  "aa" (0, 2)
찾은 문자열  "aaa" (3, 6)

(4) 한정자의 매치 방법 - greedy

위에서 살펴본 *와 +는 무한개까지 매치가 되므로 앞뒤의 문맥에 따라 묘하게 적용될 수가 있습니다. 일단 먼저 알아두어야 할 것은 ?, *와 +는 입력에서 매치되는 최대한의 부분을 찾으려고 한다는 점입니다. 먼저 물음표(?)를 살펴봅시다. 물음표는 생략 가능으로 0번 또는 1번 매치하는 패턴입니다. 다음과 같이 0번 나오는 경우도 매치되고 한번 나오는 경우도 매치됩니다.

(1) regex 먼저    (2) input 먼저   (3) 그룹찾기   (기타) 종료.. 1

regex: a?
input: abb
찾은 문자열  "a" (0, 1)
찾은 문자열  "" (1, 1)
찾은 문자열  "" (2, 2)
찾은 문자열  "" (3, 3)
input: bb
찾은 문자열  "" (0, 0)
찾은 문자열  "" (1, 1)
찾은 문자열  "" (2, 2)
input: 

그럼 예를 들어 a?a라고 하면 어떻게 될까요? 그럼 aa와 a 중에 어느 것을 우선할까요? 다음 예제를 한번 살펴봅시다.  

regex: a?a
input: abaabaaabaaaab
찾은 문자열  "a" (0, 1)
찾은 문자열  "aa" (2, 4)
찾은 문자열  "aa" (5, 7)
찾은 문자열  "a" (7, 8)
찾은 문자열  "aa" (9, 11)
찾은 문자열  "aa" (11, 13)

즉 한 개 또는 두 개의 a를 매치하는데, 일단 한번 매치된 것은 다시 보지는 않는다는 것을 알 수 있지요? 그리고 둘다 가능한 경우라면 aa를 매치시킨다는 것을 알 수 있습니다. 즉 a?는 가능하면 1번 매치하려고 하고 그게 안되면 0번 매치해서 홀수개의 a를 매치하기 위해서는 0번으로 매치됩니다. 즉 자바의 regex 엔진은 입력에 대해 이미 a를 매치한 후에, 뒤에 a가 안 나오면 매치했던 것을 도로 내놓고 (백트랙) 빈 문자열에 매치함을 알 수 있습니다. 

다음 그림은 ?, *, +에 대해 어떻게 매치되는지를 보여줍니다.

이것이 자바 regex의 기본 매치 모드인 greedy 방식입니다. 이것을 잘 이해하는 것이 중요하고 사실 가능하면 이런 문제가 생기지 않게 패턴을 작성하는 것이 좋겠지요? *나 ?가 0번을 포함하므로 그런 것들은 가능하면 뒤로 보내는 방법이 있습니다. 회수가 정해진 것을 먼저, 그다음에 +, 그 다음에 0번을 포함하는 *나 ?가 오면 매치하는 알고리듬이 예측하기가 좀더 쉽습니다.

그러나 가끔은 복잡한 패턴에서 어떻게 매치되는지를 한참 생각해 보아야 하는 경우가 생깁니다. 또한 내가 생각한 대로 매치가 되지 않는 경우에 그 원인이 이러한 ?, *, + 한정자의 적용 방식 때문인 경우가 많이 있습니다.

특히 무엇이든 나올 수 있게 하는 패턴식 부분은 .*(점 스타)인데 이것을 쓰면 뒤에서부터 매치가 되는 것을 알 수 있습니다. 예를 들어 "ch"로 끝나는 모든 문자열을 찾는 패턴은 어떻게 될까요?

regex: .*ch
input: church christmas char
찾은 문자열  "church christmas ch" (0, 19)
input: 

그런데 여기서 우리가 원하는 것이 church같은 단어를 포함해 ch로 끝나는 부분을 모두 찾는 것이었다면 패턴의 표현을 좀 다르게 해야 되겠지요?

여기서 ch로 끝나는 단어를 원한다면 좀더 간단합니다. 즉  ".*ch\s"라고 써주면 됩니다. \s는 모든 공백문자를 나타내니까 위의 문제에서 church만 찾아줄 것입니다. 이보다 더 복잡한 경우를 표현하는 방법은 다음 글에서 살펴봅니다. 즉 패턴으로 끝나는 단어라면 공백 또는 구두점 또는 입력의 제일 끝 이런 조건을 다 고려해야 되겠지요?

이 글에서 살펴본 여러 가지 한정자에 대해서는 다양한 입력과 패턴을 테스트해보면서 감을 익혀야 합니다. 이것이 사실 정규표현식의 꽃이라고 할 수 있지요.

[연습문제] 다음의 조건을 만족하는 패턴을 작성하고 다양한 입력 문자열로 테스트해 보세요. 

  1. 모든 정수를 나타내는 패턴을 작성해 보세요. 정수는 0 홀로 나오거나 0이 아닌 다른 숫자로 시작하고 여러 개의 숫자가 연속해서 나올 수 있습니다.
  2. 소수점 수를 나타내는 패턴을 작성해 보세요. 소수점 수는 정수 다음에 소수점이 나오고 임의의 숫자가 연속해서 얼마든지 반복될 수 있습니다. 소수점 다음에 아무것도 나오지 않아도 됨. ("0.1234", "0.0001", "12.1", "12." 등이 모두 만족, ".123", "00.123"은 아님)
  3. 아이디를 나타내는 패턴을 작성해 보세요. 아이디는 영문자와 숫자로 6글자 이상으로 구성되며 숫자로 시작할 수 없습니다. 
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함