지난 시간에 이어서 regular expression을 계속 다뤄보겠습니다. 만약 ‘cat’이나 ‘dog’가 포함된 문장을 찾고 싶으면 어떻게 해야 할까요? []
를 써서는 안된다는 거 아시죠? (‘c’, ‘a’, ‘t’, ‘d’, ‘o’, ‘g’ 이렇게 찾게 될 테니까요..) 이럴때는 |
파이프를 통해서 해결할 수 있습니다.
>>> import re
# '|'를 통해 패턴 자체에 or를 적용할 수 있습니다.
>>> pairs = [('cat|dog', 'cats are here'),
('cat|dog', 'dogs are there')]
>>> for pattern, string in pairs:
... s = re.search(pattern, string)
... if s:
... print s.start(), s.end(), string[s.start():s.end()]
... else:
... print "Not Found"
0 3 cat
0 3 dog
그렇다면 ‘guppy’라는 단어를 생각해봅시다. 복수형은 ‘guppies’가 될테고, 이 때 두 가지 모두를 고려해서 찾고 싶으면 어떻게 해야 할까요? |
는 패턴 하나에 적용이 되지 한 글자에 적용되는 것이 아니므로 주의하셔야 합니다!
# ()를 통해서 disjunction이 적용될 범위를 구할 수 있습니다.
>>> pairs = [('guppy|ies', 'guppies'),
('gupp(y|ies)', 'guppies')]
>>> for pattern, string in pairs:
... s = re.search(pattern, string)
... if s:
... print s.start(), s.end(), string[s.start():s.end()]
... else:
... print "Not Found"
4 7 ies
0 7 guppies
결과를 보시면 아시겠지만, 괄호 ‘()’를 치지 않은 경우에는 ‘guppy’라는 단어 또는 ‘ies’라는 단어를 찾게 됩니다. 우리가 원하는 결과를 얻으려면 'gupp(y|ies)'
라고 해 주어야 합니다.
반면에 '*'
는 원래는 한 문자에만 적용되는 것입니다. 하지만 ()
를 통해서 전체 패턴을 중복으로 검색할 수 있습니다.
# '*'는 원래 한 문자에만 적용되는 것이지만, ()를 통해서 전체 중복으로 검색할 수 있습니다.
>>> pairs = [('Column [0-9]+ *', 'Column 1 Column 2 Column 3'),
('(Column [0-9]+ *)*', 'Column 1 Column 2 Column 3')]
>>> for pattern, string in pairs:
... s = re.search(pattern, string)
... if s:
... print s.start(), s.end(), string[s.start():s.end()]
... else:
... print "Not Found"
0 9 Column 1
0 26 Column 1 Column 2 Column 3
처음 패턴은 띄어쓰기만이 *
의 영향권 아래에 있기 때문에 Column 1
만 출력하고 끝나지만, 괄호로 패턴을 묶어준 후 *
를 하게 되면 동일한 패턴의 반복을 붙여서 찾을 수 있게 됩니다. 이를 통해서 우리는 연산자에 처리 우선순위가 있다는 것을 알 수 있죠.
연산자 처리 우선순위 |
---|
1. Parenthesis () |
2. Counters * + ? {} |
3. Sequences and anchors the ^my end$ |
4. Disjunction | |
그럼 이제 좀 더 어려운 example들을 다뤄볼까요?
# Advanced examples
>>> pairs = [('\$[0-9]+', '500MHz 3.5Gb 32 Megabytes, Mac $999.99 or $1000'),
('\$[0-9]+\.[0-9][0-9]', '500MHz 3.5Gb 32 Megabytes, Mac $999.99 or $1000'),
('\$[0-9]+(\.[0-9][0-9])?', '500MHz 3.5Gb 32 Megabytes, Mac $999.99 or $1000'),
(r'\b[0-9]+ *(M[hH]z|[Mm]egahertz|G[hH]z|[gG]igahertz)\b', '500MHz 3.5Gb 32 Megabytes, Mac $999.99 or $1000'),
(r'\b[0-9]+(\.[0-9]+)? *([mM]b|[Mm]egabytes?|[gG]b|[gG]igabytes?)\b', '500MHz 3.5Gb 32 Megabytes, Mac $999.99 or $1000')]
>>> for pattern, string in pairs:
... s = re.finditer(pattern, string)
... for obj in s:
... print obj.start(), obj.end(), string[obj.start():obj.end()]
... print ''
31 35 $999
42 47 $1000
31 38 $999.99
31 38 $999.99
42 47 $1000
0 6 500MHz
7 12 3.5Gb
13 25 32 Megabytes
먼저 첫번째 예제에서는 달러 표시($
)를 포함한 연속된 숫자만 잡아내게 되어있습니다. 따라서 소수점은 잡아내지 못했죠. 반면 두번째 예제에서는 소수점을 포함한 소수 둘째자리까지 잡아내기 때문에 $999.99
만 잡아내고 $1000
은 잡아내지 못합니다. 정확히 우리가 의도한 것을 해내기 위해서는 ()
를 통해 소수점부터 두 숫자를 grouping하여 있어도 되고 없어도 되는 ?
로 처리해줘야 할 것입니다. 세번째 예제가 이를 증명하고 있죠.
네번째 예제는 ‘MHz’또는 ‘GHz’에 대한 항목을 잡아내는 부분입니다. 이제는 딱 보면 어떤 패턴을 잡아낼 지 예상이 되시죠?
마지막 다섯번째 예제는 용량 부분을 잡아내는 것인데, 소수점 이하의 자리가 하나 이상으로 처리된 부분이 조금 특별하네요. 이제 어느정도 우리도 원하는 패턴을 찾아낼 정도가 된 것 같네요 :)