정규식은 때로 퍼즐을 푸는 것과 같다.

 

세상에는 퍼즐에 특화된 천재들이 많다. 비록 실생활에서 별로 쓸모없는 재능일지라도 퍼즐 달인의 플레이는 경이롭다.

 

인간의 뇌의 능력과 잠재력을 새삼 깨닫기 때문이다. '어떻게 저렇게 퍼즐을 풀었지??'

 

정규식을 학습 과제로 생각하면 흥미를 빨리 잃어버릴 것이다. 그 보다는 퍼즐을 푸는 것처럼 접근하는 것이 빠른 습득에 도움이 된다.

 

정규식은 기계어 관점이 아닌 것임을 알아야 한다. 기계어는 0과1의 조합이다. 그것은 CPU를 위한 언어이다. 직관보다는 정확성과 논리가 중요하다. 1초에 4기가 헤르츠씩 진동하는 CPU에게 가장 단순한 일을 수억번 시키는 일이다. 단순한 일을 엄청난 속도로 반복하는 것에 CPU의 아름다움이 있다. 이전 포스팅을 보면 CPU의 파워를 알 수 있을 것이다. 

 

글쓴이의 i7-10700 시스템은 C++로 10초 동안 약 62억번 숫자를 누산할 수 있다.

 

한 30년 전처럼 주판으로 인간에게 작업을 시켰다고 가정해보자. 주판을 모르는 사람은 전자계산기, 아니 엑셀로 62억개의 숫자를 입력한다고 생각해보자.

 

지금 시대는 이 CPU의 연산 능력을 보통 사람이 가지고 있다. 기계의 연산능력은 단순하지만 상상을 초월하는 것이다.

 

 

 

C++과 파이썬 루프 | i7-10700 컴퓨터성능 테스트 2 (i7-4790 과 비교)

i7-4790을 2014년도 부터 사용하다가 6년만에 i7-10700으로 바꿨다. 램도 DDR4를 달았다. 지난번 i7-4790 과 비교에서 for문의 성능이 얼마나 좋아졌나 알아보기로 한다. 지난번 테스트는 아래와 같다. C++��

digiconfactory.tistory.com

 

반면 정규식은 인간의 언어를 다룬다. 인간의 언어는 기계어보다 훨씬 더 복잡하다. 기계는 인간의 언어를 쉽게 이해할 수 없다. 예를 들면 사람에게 '너 밥먹었니?' 라고 물어보는 것과 '배 안고프니?' 라고 물어보면 어린이도 순식간에 그 뜻을 알 수 있다.

 

그런데 CPU는 이 시멘틱(의미) 질문을 한번의 연산으로는 이해할 수 없다. 예를 들어 지금 유행하는 AI 강화학습을 받아야 알 수 있을 것이다. AI 강화학습은 수많은 연산이 필요하다. 인간은 그럴 필요가 없다.

 

정규식은 인간의 언어(문자열)에 포커스를 맞췄기 때문에 내부적으로는 많은 연산이 필요하다. 하지만 인간은 보면 의미를 알 수 있다. 01011101 처럼 보이는 기계어는 코드만 봐선 이해가 안되는데 정규식을 보면 의미를 알 수 있다.

 

기계와 인간의 차이를 생각하면서 정규식의 코드를 보면 인간 두뇌와 CPU의 차이에 대해 더 알게 될 것이다.

 

이 포스팅에는 다양한 예제 코드를 보기로 한다. 

 

문법적인 부분 보다는 실제 코드의 테크닉에 집중한다면 습득이 빠를 것이다.

 

<코딩은 공부가 아닙니다. 연습입니다 - 코딩도장->

 

import re는 모든 코드에 공통이므로 생략한다.

 

regex = re.compile(r'\d+\s\w+')
list = regex.findall('1 apple, 5 oranges, 3 boys, 4 girls; 10 army| 11 mr')
print(list)

d+ 는 1개 이상의 숫자, s는 공백문자 w+는 단어를 의미한다.


customRegex = re.compile(r'[aeiouAEIOU]')
list = customRegex.findall('Hello my friend! Life is short you need Python!')
print(list)

customRegex = re.compile(r'[^aeiouAEIOU]')
list = customRegex.findall('Hello my friend! Life is short you need Python!')
print(list)

영문자 모음과 자음을 분리해서 추출한다. [ ] 안의 ^ 은 부정이다. findall 은 search 와 달리 리스트를 반환한다는 점에 주의한다.

 


regex = re.compile(r'^home')
mo1 = regex.search('we are going home')
if mo1 == None:
    print("no match found")
else:
    print(mo1.group())
mo2 = regex.search('home sweet')
if mo2 == None:
    print("no match found")
else:
    print(mo2.group())

regex = re.compile(r'home$')
mo1 = regex.search('we are going home')
if mo1 == None:
    print("no match found")
else:
    print(mo1.group())
mo2 = regex.search('home sweet')
if mo2 == None:
    print("no match found")
else:
    print(mo2.group())

문자열 앞의 ^ 캐롯 문자는 이 단어로 시작할 경우 match 한다. $ 달러 문자는 이 단어로 끝나야 match 한다.

 


regex = re.compile(r'^\d+$')
mo = regex.search('199305')
if mo == None:
    print('no match found')
else:
    print(mo.group())

mo = regex.search('99food234')
if mo == None:
    print('no match found')
else:
    print(mo.group())

^ 캐롯과 $ 달러로 감싼 d+ 는 1개 이상의 숫자에 match 한다. 중간에 문자가 있으면 match 가 안된다. 두번째 조건을 match 시킬려면 아래와 같이 컴파일 한다. \D는 숫자문자 이외의 문자.

regex = re.compile(r'^\d+\D+\d+$')

. 도트문자는 줄바꿈을 제외한 모든 문자와 같다. 아래와 같이 컴파일 할 수 있다. {1,7}은 문자수의 범위가 1부터 7까지 이다.

regex = re.compile(r'\d:.{1,7}man')
list = regex.findall('1:fireman 2:policeman 3:vectorman 4:gentleman 5:newman')
for i in list:
    print(i)


줄바꿈 문자의 조작. 일반적으로 . 도트(모든 것) * 별(0개 이상)을 컴파일하면 줄바꿈 문자 \n 전까지 매치한다. 줄바꿈 문자가 있는 파일의 모든 범위를 매치하려면 compile 메소드에 두번째 매개변수를 re.DOTALL 로 지정한다. 전체 텍스트에 매치할 수 있다.

regex = re.compile('.*')
mo = regex.search('what are you doing?\nit is going to be late for school'
             '\nwe need to hurry up')
print(mo.group())

print('----------------------')

regex = re.compile('.*',re.DOTALL)
mo = regex.search('what are you doing?\nit is going to be late for school'
             '\nwe need to hurry up')
print(mo.group())


대소문자를 구분하지 않고 매치시키려면 compile 메소드의 두번째 매개변수를 re.I로 넘겨준다. (re 모듈의 상수다)

regex = re.compile(r'friend',re.I)
list = regex.findall('Friend fRiend friEnd FRIEND')
for i in list:
    print(i)

대소문자는 아스키코드가 다름에도 매치가 되는 것을 볼 수 있다.

 


정규식으로 찾은 문자열을 대체할 수 있다. sub 메소드를 사용한다.

 

아래의 예제는 컴파일한 apple 문자열을 TOMATO 로 대체한다.

regexSub = re.compile(r'apple')
str1 = regexSub.sub('TOMATO','we love to eat apple, fresh apple in the morning is '
                             'my favorite dessert')
print(str1)

regex = re.compile(r'apple (\w)\w*')
mo = regex.search('We just love apple A')
print(mo.group())
str1 = regex.sub(r'\1XXX','We just love apple banana')
print(str1)

두번째 출력에서는 다른 문자열로 대체할 뿐 아니라 \1은 mo.group(1)의 문자열로 대체하는 것이다.

 

정규식을 잘 보면 (\w)w* 로 그룹을 나눈다. (\w)가 1번 그룹이다. banana 가 b 와 anana 로 분리되는 것이다.

 


짧은 지면에 상당히 많은 정규식 예제를 담았다. 그냥 보는 것은 의미가 없고 코드를 직관적으로 이해하고 조작을 해보는 것이 도움이 된다. 텍스트문과 정규식을 하나씩 수정하여 실행해보면 도움이 된다.

 

파이썬 입문자들에겐 정규식이 어려운 내용이다. 처음에 잘 모르더라도 괜찮다.

 

정규식을 모르고 살기엔 너무 유용한 부분이 많다.

 

퍼즐을 맞추는 기분으로 자기의 프로그램을 짜기 위해서 연습을 하다보면 금방 실력이 향상될 것이다.

공유하기

facebook twitter kakaoTalk kakaostory naver band