파이썬 문자열 필수 스킬 - N-gram 순회하기, 대소문자를 무시한 변환
1. N-gram 순회하기
예를 들어 문자열 'abcdefabcxydzedase'가 있다고 해보자.
일반적으로 for문을 이용해 순회하면 1글자씩 순회하는데 이는 1-gram이라고 부른다
string = 'abcdefabcxydzedase'
for char in string:
print(char)
a
b
c
d
e
f
a
b
c
x
y
d
z
e
d
a
s
e
그런데 알고리즘 문제를 푸는 코딩테스트를 보다보면 2-gram 이상으로 순회하고 싶을 때가 있다.
예를 들어 'abcdefabcxydzedase'에서 ab, bc, cd, de, ef, fa, ab, bc, cx, xy,.... 방식으로 순회하거나
abc, bcd, cde, def, ...로 순회하거나
어떻게 가능할까?
zip함수를 이용하면 2-gram은 쉽게 가능할 것 같다
for char1, char2 in zip(string,string[1:]):
print(char1+char2)
ab
bc
cd
de
ef
fa
ab
bc
cx
xy
yd
dz
ze
ed
da
as
se
그러면 3-gram도 zip(string,string[1:], string[2:])로 순회할 수 있을 것 같은데
for char1, char2, char3 in zip(string,string[1:],string[2:]):
print(char1+char2+char3)
abc
bcd
cde
def
efa
fab
abc
bcx
cxy
xyd
ydz
dze
zed
eda
das
ase
이 방식은 사실 아주 큰 문제점이 있다
코딩테스트를 풀다보면 n-gram까지 순회하고 싶은데 n이 어디까지가 될지를 모른다는 점이다
게다가 zip(string,string[1:],string[2:],....)에서 string, string[1:],.... 이걸 ngram에 따라 자동으로 입력하는 것은 불가능하다???
라고 생각했는데 자동으로 입력하는 법이 있었네
[string[i:] for i in range(n)]을 하면 string[0:], string[1:], string[2:],...., string[n:]가 자동으로 리스트 안에 들어간다
[string[i:] for i in range(4)]
['abcdefabcxydzedase',
'bcdefabcxydzedase',
'cdefabcxydzedase',
'defabcxydzedase']
이를 zip으로 묶어버리면 된다
list(zip([string[i:] for i in range(4)]))
[('abcdefabcxydzedase',),
('bcdefabcxydzedase',),
('cdefabcxydzedase',),
('defabcxydzedase',)]
하지만 단순이 zip으로 묶으면 내가 원하는 4-gram이 나오지 않는다
이럴 때는 asterisk인 *을 붙여서 unpacking을 시켜서
[string[i:] for i in range(4)] = ['abcdefabcxydzedase','bcdefabcxydzedase','cdefabcxydzedase','defabcxydzedase']
여기에서 *을 붙이면 zip 함수에 list가 풀어진
'abcdefabcxydzedase','bcdefabcxydzedase','cdefabcxydzedase','defabcxydzedase' 상태로 들어가서
zip('abcdefabcxydzedase','bcdefabcxydzedase','cdefabcxydzedase','defabcxydzedase') 이렇게 되면 4개의 문자열을 묶어서 순회하게 된다
그래서 'abcdefabcxydzedase'의 첫번째 a, 'bcdefabcxydzedase'의 첫번째 b, 'cdefabcxydzedase'의 첫번째 c, 'defabcxydzedase'의 첫번째 d가 묶이고... 두번째 문자끼리 묶이고... 세번째 문자끼리 묶이고...
list(zip(*[string[i:] for i in range(4)]))
[('a', 'b', 'c', 'd'),
('b', 'c', 'd', 'e'),
('c', 'd', 'e', 'f'),
('d', 'e', 'f', 'a'),
('e', 'f', 'a', 'b'),
('f', 'a', 'b', 'c'),
('a', 'b', 'c', 'x'),
('b', 'c', 'x', 'y'),
('c', 'x', 'y', 'd'),
('x', 'y', 'd', 'z'),
('y', 'd', 'z', 'e'),
('d', 'z', 'e', 'd'),
('z', 'e', 'd', 'a'),
('e', 'd', 'a', 's'),
('d', 'a', 's', 'e')]
그러므로 각 리스트의 원소 하나씩 순회해서 ''.join()을 이용해 문자열로 만들면 4-gram 순회를 할 수 있다
for str_tuple in list(zip(*[string[i:] for i in range(4)])):
gram4 = ''.join(str_tuple)
print(gram4)
abcd
bcde
cdef
defa
efab
fabc
abcx
bcxy
cxyd
xydz
ydze
dzed
zeda
edas
dase
2. 대소문자를 무시하고 문자열 변환하기
문자열 변환 함수하면 replace가 생각이 나서 replace만 활용하고자 하는데
오늘 코딩테스트 푸는데 이런 문제를 만났다.
'AbBCaBabbcAbABA'에서 대소문자를 상관하지 않고 'ab'에 해당하는 부분을 제거한 문자열 'BCbcA'를 반환한다면?
어떻게 가능할까???
단순히 replace만 활용하면 'ab'밖에 제거 못한다
string = 'AbBCaBabbcAbABA'
string.replace('ab','')
AbBCaBbcAbABA
그렇다고 'Ab', 'AB', 'aB'를 모두 생각해서 전부 제거할 것인가???
그럴수도 있지만 이게 문자열이 더 길어진다면??? 예를 들어 'abc'를 대소문자 구분하지 않고 제거하라한다면??
'ABC','aBC','abC','Abc',...... 이걸 다 고려한다고??
프로그래밍이 가능해???
이럴때는 re모듈의 re.sub를 활용해서 문자열을 변환할 수 있다
그런데 이 모듈이 지원하는 부분이 바로 대소문자를 무시할 수 있다
여기서 flags 인자를 주목할 필요가 있다
flags로 줄 수 있는 옵션이
바로 re.IGNORECASE를 flags인자에 주면 re.sub함수가 대소문자를 구별하지 않고 매칭되는 패턴을 제거시킬 수 있다
바로 사용해보면
import re
re.sub('ab','',string,flags=re.IGNORECASE)
BCbcA
그래서 replace만 생각할 것이 아니라 re 모듈의 sub함수가 있다는 점
문자열 활용할 때 re 모듈을 활용할 생각을 반드시 고려해봐야한다는 점 기억할 것
3. 출처
https://dojang.io/mod/page/view.php?id=2332
https://dojang.io/mod/page/view.php?id=2345
https://docs.python.org/ko/3/library/re.html
'알고리즘 > 알고리즘 일반' 카테고리의 다른 글
중복을 허용하는 집합 다루기 (0) | 2022.04.17 |
---|---|
진수 변환 알고리즘 활용하기 (0) | 2022.04.16 |
그래프를 여러 집단으로 나누는 방법 (0) | 2022.04.09 |
파이썬 중복을 제거하는 set 잘 활용하기 (0) | 2022.04.06 |
2가지 모드로 나누어서 생각하기(코딩테스트 복기) (0) | 2022.04.01 |