파이썬에서 문자열을 처리하는 방법은 여러가지가 있죠. 그 중 정규표현식을 이용하면 좀 더 다양하고 쉽게 처리를 할 수 있습니다.
그래서 이번 글에서는 파이썬에서 유용한 모듈 중 정규식을 처리해주는 re 모듈을 이용해서 문자열을 처리하는 방법에 대해 알아보려고 합니다.
그리고 가장 마지막엔 배운걸 토대로 카카오 문제인 [3차] 파일명 정렬에 적용해보려고 합니다.
그럼 렛츠두더코드!
re 모듈의 함수들
re 모듈에는 다양한 함수들이 존재합니다. 그 중 알고리즘 풀이에 유용할 것 같은 함수들에 대해 알아보겠습니다.
- search
- match
- fullmatch
- findall
- finditer
- sub
- subn
- split
search()
문자열에서 패턴이 일치하는 지 확인합니다.
print(re.search("A", "A-10521 IronMan Mark II").group())
# A
print(re.search("[a-zA-Z]+", "A-10521 IronMan Mark II").group())
# A
match()
문자열의 처음부터 시작해 패턴과 일치하는 지 확인합니다.
re.match("A", "A-10521 IronMan Mark II").group()
# A
re.match("B", "A-10521 IronMan Mark II").group()
# Error
첫번째 같은 경우 입력 받은 문자열이 A 로 시작하기 때문에 바로 A 를 매칭해줍니다.
두번째 같은 경우 문자열이 A 로 시작하는데 패턴을 B 로 매칭했기 때문에 group() 으로 찾을 수 없어 에러를 토해냅니다.
정규식에는 그룹이라는 개념이 존재하는데 그 그룹을 출력하려면 group() 함수를 이용해야함
그럼 이번에는 정규식을 이용해서 "A-10521 IronMan Mark II" 에서 "A-10521" 만 추출해보죠.
re.match("\S+", "A-10521 IronMan Mark II").group()
re.match("[0-9a-zA-Z-]+", "A-10521 IronMan Mark II").group()
re.match("[0-9a-zA-Z-]{0,}", "A-10521 IronMan Mark II").group()
re.match("[0-9a-zA-Z-]{0,7}", "A-10521 IronMan Mark II").group()
re.match(".{0,7}", "A-10521 IronMan Mark II").group()
# A-10521
위에 있는 방식 모두 같은 "A-10521" 을 출력합니다.
왜 저렇게 되는지는 파이썬 re 모듈에 해당하기보다는 정규식 패턴을 알아야 하는 부분인데 간단하게 한번 살펴보죠!
그 전에 정규식에는 "[].\*" 와 같은 메타 문자가 존재하는데 각 메타문자를 무엇을 의미하는지 살펴보겠습니다.
그리고 "\" 를 활용한 표현법도 있습니다.
그럼 이제 간단하게 살펴보죠.
re.match("\S+", "A-10521 IronMan Mark II").group()
# 띄어쓰기가 아닐때까지(\S) 1번부터 무한대까지(+) 표현
re.match("[0-9a-zA-Z-]+", "A-10521 IronMan Mark II").group()
# 0~9, a~z, A~Z, - 일때까지 1번부터 무한대까지(+) 표현
re.match("[0-9a-zA-Z-]{0,}", "A-10521 IronMan Mark II").group()
# 0~9, a~z, A~Z, - 일때까지 0번부터 무한대까지({0,}) 표현
re.match("[0-9a-zA-Z-]{0,7}", "A-10521 IronMan Mark II").group()
# 0~9, a~z, A~Z, - 일때까지 0번부터 7번까지({0,7}) 표현
re.match(".{0,7}", "A-10521 IronMan Mark II").group()
# 줄바꿈을 제외한 모든 문자(.)까지 0번부터 7번까지({0,7}) 표현
fullmatch()
match() 같은 경우 문자열의 첫번째 인덱스만 맞으면 되지만, fullmatch() 는 마지막도 일치해야합니다.
re.fullmatch("A-10521 IronMan Mark II","A-10521 IronMan Mark II").group()
# A-10521 IronMan Mark II
re.fullmatch("[0-9a-zA-Z- ]+","A-10521 IronMan Mark II").group()
# A-10521 IronMan Mark II
re.fullmatch("[0-9a-zA-Z- ]+","A-10521 IronMan Mark II?")
# None
# 문자열 가장 뒤에 ? 가 있는데 패턴에는 ? 처리가 없기 때문에 None 처리
findall()
문자열 안에 패턴과 일치하는 값들을 전부 찾아서 리스트로 반환합니다.
re.findall("\d", "A-10521 IronMan Mark II")
# ['1', '0', '5', '2', '1']
# \d 에 {} 가 없기 때문에 숫자를 하나씩 찾아서 리스트로 반환
re.findall("\d+", "A-10521 IronMan Mark II")
# ['10521']
# \d 에 + 가 있기 때문에 숫자를 찾은 다음 숫자가 아닐때까지 찾는 것을 반복하고 리스트로 반환
re.findall("\d+", "A-10521 B-20179 IronMan Mark II")
# ['10521', '20179']
# \d 에 + 가 있기 때문에 숫자가 아닐때까지 찾은 다음 숫자가 아닐때까지 찾는 것을 반복하고 리스트로 반환
re.findall("\w+", "A-10521 IronMan Mark II"
# ['A', '10521', 'IronMan', 'Mark', 'II']
# 줄바꿈을 제외하고 문자나 숫자가 아닐때까지 찾은 다음 리스트로 반환
finditer()
findall() 과 비슷하지만 리스트가 아닌 iterator 로 반환합니다.
for i in re.finditer("\w+", "A-10521 IronMan Mark II"):
print(i.group())
# A
# 10521
# IronMan
# Mark
# II
sub()
문자열에서 어떤 문자를 어떤 문자로 바꿀 것인지를 찾아 문자열을 반환합니다.
print(re.sub("I","K", "A-10521 IronMan Mark II"))
# A-10521 KronMan Mark KK
# I 를 모두 찾아서 K 로 변환
print(re.sub("I","K", "A-10521 IronMan Mark II", 1))
# A-10521 KronMan Mark II
# I 를 1개만 찾아서 K 로 변환
print(re.sub("[A-Z]+","&", "A-10521 IronMan Mark II"))
# &-10521 &ron&an &ark &
# A ~ Z 문자열을 찾아 & 로 변환
print(re.sub("[A-Z]+","&", "A-10521 IronMan Mark II", 1))
# &-10521 IronMan Mark II
# A ~ Z 문자열을 찾아 1개만 & 로 변환
subn()
sub() 와 유사하지만 변환된 문자열뿐만 아니라 바꾼 갯수까지 튜플로 반환합니다.
re.subn("I", "K", "A-10521 IronMan Mark II")
# ('A-10521 KronMan Mark KK', 3)
re.subn("I", "K", "A-10521 IronMan Mark II", 1)
# ('A-10521 KronMan Mark II', 1)
re.subn("[A-Z]+","&", "A-10521 IronMan Mark II")
# ('&-10521 &ron&an &ark &', 5)
re.subn("[A-Z]+","&", "A-10521 IronMan Mark II", 1)
# ('&-10521 IronMan Mark II', 1)
split()
문자열에서 주어진 패턴으로 split 한 후 리스트로 반환합니다.
print(re.split("A", "A-10521 IronMan Mark II"))
# ['', '-10521 IronMan Mark II']
print(re.split("[a-zA-Z]+", "A-10521 IronMan Mark II"))
# ['', '-10521 ', ' ', ' ', '']
print(re.split("[a-zA-Z]+", "A-10521 IronMan Mark II", 1))
# ['', '-10521 IronMan Mark II']
함수들이 어떻게 동작하는지 예제와 한번 살펴보았는데요.
실제 문제에 어떻게 적용하는지 알아보죠!
문제풀이
문제는 2018 KAKAO BLIND [3차] 파일명 정렬 입니다.
문제에 대한 설명은 생략하겠습니다.
문제 조건
- HEAD 는 대소문자 구분 X, 문자만
- NUMBER 는 1~5 글, 앞에 0이 올 수 있음
- TAIL 은 빈 문자열일 수 있고, 숫자랑 문자가 섞임
- HEAD 부분과, NUMBER의 숫자도 같을 경우, 그냥 들어오는 순서대로
- 0 의 갯수만 다른 중복된 텍스트가 있을 수 있음
import re
def solution(files):
return sorted(files, key=lambda file: (find_head_and_number(file)))
def find_head_and_number(file):
head, number, tail = re.match("([a-zA-Z-. ]+)(\d{1,5})(.*)", file).groups()
return [head.lower(), int(number)]
여기서 핵심 코드는 이겁니다.
re.match("([a-zA-Z-. ]+)(\d{1,5})(.*)", file).groups()
re.match 를 사용하여 처음부터 패턴과 일치하는 지 살펴보게 되는데,
-, . 를 포함한 문자열을 찾고, 그 후엔 1~5개까지의 숫자를 찾은 후, 그 후엔 빈 문자나 아무 문자를 찾은 다음 groups() 를 이용해서 튜플로 반환합니다.
나머지는 아시리라 믿고 따로 설명하지 않겠습니다.
re 모듈을 이용하면 문자열 문제를 해결하는데 많은 도움이 될 것 같습니다.
그럼 오늘은 여기까지!
'알고리즘 > 유용한 파이썬 함수' 카테고리의 다른 글
[Python] Counter 모듈 사용법 feat. 카카오 튜플 (0) | 2023.03.12 |
---|