파이썬 | 회문 판별과 N-gram 만들기

CHOI·2021년 11월 24일
0

Python

목록 보기
21/33
post-thumbnail

이번에는 문자열을 응용하여 회문을 판별하는 방법과 N-gram을 만드는 방법에 대해서 알아보자

1. 회문 판별하기

회문(palindrome)은 순서를 거꾸로 읽어도 제대로 읽은 것과 같은 단어와 문장을 말한다. 예를 들어 "level", "SOS", "rotator", "nurses run"과 같은 단어와 문장이 있다.

그럼 문자열이 회문인지 판별하기 위해선 어떻게 할까? 회문은 첫 글자와 마지막 글자가 같다. 그리고 안쪽으로 한 글자 씩 좁혔을 때 글자가 같아서 서로 같으면 회문이다.

Untitled

반복문으로 문자 검사하기

word = input('단어를 입력하세요: ')
 
is_palindrome = True                 # 회문 판별값을 저장할 변수, 초깃값은 True
for i in range(len(word) // 2):      # 0부터 문자열 길이의 절반만큼 반복
    if word[i] != word[-1 - i]:      # 왼쪽 문자와 오른쪽 문자를 비교하여 문자가 다르면
        is_palindrome = False        # 회문이 아님
        break
 
print(is_palindrome)                 # 회문 판별값 출력

실행 결과

단어를 입력하세요: level (입력)
True

실행 결과

단어를 입력하세요: hello (입력)
False

여기서 핵심인 개념은 문자열 길이이다. 판별을 할 때 문자열 길이를 기준으로 절반으로 나누어 왼쪽 문자와 오른쪽 문자가 같은지 검사한다.

Untitled

시퀀스 뒤집기로 문자열 검사하기

반복문으로 문자열의 각 문자를 일일이 비교하려니까 다소 번거롭다. 이 보다 더 간단한 방법이 있다.

회문은 시퀀스 객체의 슬라이스를 활용하면 간단하게 판별할 수 있다.

word = input('단어를 입력하세요: ')
 
print(word == word[::-1])    # 원래 문자열과 반대로 뒤집은 문자열을 비교

실행 결과

단어를 입력하세요: level (입력)
True
단어를 입력하세요: hello (입력)
False

정말 간단하게 word = word[::-1] 로 간단하게 끝났다. word[::-1] 은 문자열 전체에서 인덱스가 1 씩 감소하면서 요소를 가져오기 때문에 문자열이 뒤집혀지게 된다.

리스트와 reverse 사용하기

이 방법 외에도 다양한 방법으로 회문을 판별할 수 있는데 반복 가능한 객체의 요소 순서를 반대로 뒤집는 reversed 를 사용해도 된다.

>>> word = 'level'
>>> list(word) == list(reversed(word))
True

reversed 를 활용하면 문자열이 반대로 뒤집히고 리스트에 넣으면 요소 순서가 반대로 된 리스트를 구할 수 있을 것이다.

>>> list(word)
['l', 'e', 'v', 'e', 'l']
>>> list(reversed(word))
['l', 'e', 'v', 'e', 'l']

이 두 리스트를 == 로 비교하면 회문인지를 판별할 수 있다.

문자열의 join 메서드와 reversed 사용하기

join 메서드를 사용하여 회문을 판별할 수 있다.

>>> word = 'level'
>>> word == ''.join(reversed(word))
True

join 은 구분자 문자열과 문자열 리스트 요소를 연결한다. 여기서는 구분자 문자열이 '' 로 빈 문자열이기 때문에 문자 순서가 반대로 된 문자열을 얻을 수 있는 것이다.

>>> word
'level'
>>> ''.join(reversed(word))
'level'

그렇기 때문에 == 로 비교하여 회문인지를 판별할 수 있다.

2. N-gram 만들기

N-gram은 문자열에서 N 개의 연속된 요소를 추출하는 방법이다. 만약 'Hello' 문자열을 2-gram으로 추출하면 다음과 같이 된다.

He
el
ll
lo

즉 문자열을 처음부터 끝까지 한 글자씩 이동하면서 2글자를 추출한다. 3-gram 은 3글자, 4-gram 은 4글자이다.

반복문으로 N-gram 출력

'Hello' 를 2-gram으로 출력해보자.

text = 'Hello'
 
for i in range(len(text) - 1):             # 2-gram이므로 문자열의 끝에서 한 글자 앞까지만 반복함
    print(text[i], text[i + 1], sep='')    # 현재 문자와 그다음 문자 출력

실행 결과

He
el
ll
lo

생각보다 매우 간단하다.

Untitled

만약 3-gram 이라면 어떻게 될까? 반복 횟수는 range(len(text) - 2) 가 되고 print(text[i], text[i + 1], text[i + 2], sep='') 가 될 것이다.

zip 으로 2-gram 만들기

text = 'hello'
 
two_gram = zip(text, text[1:])
for i in two_gram:
    print(i[0], i[1], sep='')

실행 결과

He
el
ll
lo

지금까지 zip 함수는 리스트 두 개로 딕셔너리를 만들 때 사용했는데 zip 은 반복 가능한 객체의 각 요소들을 튜플로 묶어준다.

>>> text = 'hello'
>>> list(zip(text, text[1:]))
[('h', 'e'), ('e', 'l'), ('l', 'l'), ('l', 'o')]

zip(text, text[1:])texttext[1:] 의 각 요소를 묶어서 튜플로 만든다. 따라서 위와 같이 두 리스트가 묶인 것을 볼 수 있다. 이렇게 묶인 것들을 반복문으로 출력하면된다.

zip 과 리스트 표현식으로 N-gram 만들기

앞서 zip 을 통해서 N-gram 을 만들 때 일일히 [1:], [2:] 와 같이 슬라이스를 해줘야하니 상당히 번거롭다. N-gram 의 숫자가 늘어나면 그만큼 슬라이스도 여러 개 입력해줘야 한다. 그렇다면 이 과정을 코드로 만들 수 없을까? 다음과 같이 리스트 표현식을 사용하면 된다.

>>> text = 'hello'
>>> [text[i:] for i in range(3)]
['hello', 'ello', 'llo']

위의 소스 코드를 통해서 text[0:], text[1:], text[2:] 를 구했다. 즉, 3-gram에 필요한 슬라이스이다.
이제 이 ['hello', 'ello', 'llo'] 리스트를 zip 에 넣어보자.

>>> list(zip(['hello', 'ello', 'llo']))
[('hello',), ('ello',), ('llo',)]

결과를 보면 우리가 원했던 3-gram 이 아니다. 왜 그럴까? zip 은 반복 가능한 객체를 콤마로 구분하여 넣어줘야 한다. 하지만 우리가 넘긴 ['hello', 'ello', 'llo'] 는 요소 3개가 들어있는 1개의 리스트이기 때문이다.

zip 에 각 요소를 콤마로 구분해서 넣어주려면 리스트 앞에 * 를 붙여줘야 한다.

>>> list(zip(*['hello', 'ello', 'llo']))
[('h', 'e', 'l'), ('e', 'l', 'l'), ('l', 'l', 'o')]

이제 3-gram 리스트가 만들어졌다.

리스트에 * 을 붙이는 방법을 리스트 언패킹(list unpacking)이라고 하는데 이 부분은 나중에 뒤에서 보자

지금까지 회문 판별과 N-gram 에 대해서 보았다. 여기서 인덱스로 문자열을 다루는 방법과 reversed와 zip 을 활용한 방법에 대해서 눈여겨 보아두자.

profile
벨로그보단 티스토리를 사용합니다! https://flight-developer-stroy.tistory.com/

0개의 댓글