아이엠 !나이롱맨😎
article thumbnail
반응형

 

파이썬에서 문자열을 처리하는 방법은 여러가지가 있죠. 그 중 정규표현식을 이용하면 좀 더 다양하고 쉽게 처리를 할 수 있습니다.

그래서 이번 글에서는 파이썬에서 유용한 모듈 중 정규식을 처리해주는 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차] 파일명 정렬 입니다.

문제에 대한 설명은 생략하겠습니다.

 

문제 조건

  1. HEAD 는 대소문자 구분 X, 문자만
  2. NUMBER 는 1~5 글, 앞에 0이 올 수 있음
  3. TAIL 은 빈 문자열일 수 있고, 숫자랑 문자가 섞임
  4. HEAD 부분과, NUMBER의 숫자도 같을 경우, 그냥 들어오는 순서대로
  5. 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 모듈을 이용하면 문자열 문제를 해결하는데 많은 도움이 될 것 같습니다.

 

그럼 오늘은 여기까지!

반응형

article next thumbnail
profile on loading

Loading...