기본적으로 python의 장점 중 하나는 다양한 라이브러리를 사용할 수 있다는 점이라고 생각한다.
하지만 영어를 한국어로 번역해주는 영한 번역 라이브러리는 있지만 영타를 한국어로 번역해주는 라이브러리는 없었다.
그래서 이번 시간에는 영타를 한국어로 번역해주는 코드를 직접 작성하기로 했다.
_CHO_ = 'ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ'
_JUNG_ = 'ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ'
_JONG_ = 'ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ' # index를 1부터 시작해야 함
먼저 첫 번째로 우리 한국어에서 나올 수 있는 모든 초성, 중성, 종성을 다 종합해서 정리를 했다.
우리는 위에 나온 CHO JUNG JONG 세 변수를 이용해서 다양한 한국어를 구사할 예정이다.
_JAMO2ENGKEY_ = {
'ㄱ': 'r',
'ㄲ': 'R',
'ㄴ': 's',
'ㄷ': 'e',
'ㄸ': 'E',
'ㄹ': 'f',
'ㅁ': 'a',
'ㅂ': 'q',
'ㅃ': 'Q',
'ㅅ': 't',
'ㅆ': 'T',
'ㅇ': 'd',
'ㅈ': 'w',
'ㅉ': 'W',
'ㅊ': 'c',
'ㅋ': 'z',
'ㅌ': 'x',
'ㅍ': 'v',
'ㅎ': 'g',
'ㅏ': 'k',
'ㅐ': 'o',
'ㅑ': 'i',
'ㅒ': 'O',
'ㅓ': 'j',
'ㅔ': 'p',
'ㅕ': 'u',
'ㅖ': 'P',
'ㅗ': 'h',
'ㅘ': 'hk',
'ㅙ': 'ho',
'ㅚ': 'hl',
'ㅛ': 'y',
'ㅜ': 'n',
'ㅝ': 'nj',
'ㅞ': 'np',
'ㅟ': 'nl',
'ㅠ': 'b',
'ㅡ': 'm',
'ㅢ': 'ml',
'ㅣ': 'l',
'ㄳ': 'rt',
'ㄵ': 'sw',
'ㄶ': 'sg',
'ㄺ': 'fr',
'ㄻ': 'fa',
'ㄼ': 'fq',
'ㄽ': 'ft',
'ㄾ': 'fx',
'ㄿ': 'fv',
'ㅀ': 'fg',
'ㅄ': 'qt'
}
이후 각 자음, 모음에 대응되는 영어 알파벳을 설정했다.
예시로 'ㄱ'의 경우 'r', 'ㅆ'의 경우 'T' 이런 방식으로 설정했다.
def is_hangeul_syllable(ch):
'''한글 음절인지 검사
'''
if not isinstance(ch, str):
return False
elif len(ch) > 1:
ch = ch[0]
return 0xAC00 <= ord(ch) <= 0xD7A3
다음으로 한글 음절인지 검사하는 is_hangeil_syllable 함수를 만들었다.
이 함수는 간단하게 유니 코드를 이용해서 현재 음절이 한글 음절인지 확인하는 방식으로 만들었다.
def compose(cho, jung, jong):
'''초성, 중성, 종성을 한글 음절로 조합
cho : 초성
jung : 중성
jong : 종성
return value: 음절
'''
if not (0 <= cho <= 18 and 0 <= jung <= 20 and 0 <= jong <= 27):
return None
code = (((cho * 21) + jung) * 28) + jong + 0xAC00
return chr(code)
그 다음으로 초성, 중성, 종성을 조합해서 하나의 음절로 만들어 주는 compose 함수를 만들었다.
이 역시 유니 코드를 이용해서 작성을 했다.
#input: 음절
#return: 초, 중, 종성
def decompose(syll):
'''한글 음절을 초성, 중성, 종성으로 분해
syll : 한글 음절
return value : tuple of integers (초성, 중성, 종성)
'''
if not is_hangeul_syllable(syll):
return (None, None, None)
uindex = ord(syll) - 0xAC00
jong = uindex % 28
jung = ((uindex - jong) // 28) % 21
cho = ((uindex - jong) // 28) // 21
return (cho, jung, jong)
초성, 중성, 종성을 하나의 음절로 조합하는 기능이 있다면 반대로 하나의 음절을 초성, 중성, 종성으로 나누어주는 decompose 함수도 만들었다.
이 함수는 위에 있는 compose 함수를 살짝만 수정하면 만들 수 있다.
def str2jamo(str):
'''문자열을 자모 문자열로 변환
'''
jamo = []
for ch in str:
if is_hangeul_syllable(ch):
cho, jung, jong = decompose(ch)
jamo.append( _CHO_[cho])
jamo.append( _JUNG_[jung])
if jong != 0:
jamo.append( _JONG_[jong-1])
else:
jamo.append(ch)
return ''.join(jamo)
이제 본격적으로 영타를 한국어로 바꾸는 작업을 시작할 예정이다.
먼저 문자열을 한글 자음, 모음 문자열로 변환하는 str2jamo 함수를 만들었다.
문자열을 입력 받았을 때, 각 문자열의 글자마다 먼저 한글 음절인지 확인하고 한글 음절이면 decompose를 통해 음절로 분해 시키는 작업을 한다.
이후 각 음절을들 모와 return 시키는 작업을 했다.
def jamo2engkey(jamo):
engkey = []
for ch in jamo:
if ch in _JAMO2ENGKEY_:
engkey.append(_JAMO2ENGKEY_[ch])
else:
engkey.append(ch)
return ''.join(engkey) #ekfrrhrl
다음으로 자음, 모음을 영타로 바꾸는 jamo2engkey 함수를 만들었다.
우리가 앞에서 JAMO2ENGKEY dictionary를 만들었기 때문에 이 dictionary를 이용해서 쉽게 변환할 수 있다.
def engkey2jamo(engkey):
inv_dict = {v: k for k, v in _JAMO2ENGKEY_.items()}
jamo = []
for eng in engkey:
if eng in inv_dict:
jamo.append(inv_dict[eng])
else:
jamo.append(eng)
return ''.join(jamo) #ㄷㅏㄹㄱㄱㅗㄱㅣ
자음, 모음을 영타로 바꾸는 jamo2engkey와 만대로 영타를 자음, 모음으로 바꾸는 engkey2jamo 함수 또한 만들었다.
def jamo2syllable(jamo):
inv_dict = {v: k for k, v in _JAMO2ENGKEY_.items()}
cjj = []
res =[]
first_mo = []
first_ja = []
for ch in jamo:
if ch in _JAMO2ENGKEY_:
if len(cjj) == 0 and ch in _CHO_: #초성
cjj.append(ch)
if len(first_mo) == 1:
res.append(first_mo[0])
elif len(first_mo) == 2:
double_eng = jamo2engkey(first_mo[0] + first_mo[1])
if double_eng in inv_dict:
double_jung = inv_dict[double_eng]
res.append(double_jung)
else:
res.append(first_mo[0])
res.append(first_mo[1])
first_mo = []
first_ja.append(ch)
elif len(cjj) == 0 and ch in _JUNG_:
first_mo.append(ch)
elif len(cjj) == 1 and ch in _JUNG_: #중성
cjj.append(ch)
if len(first_ja) == 2:
res.append(first_ja[0])
first_ja = []
elif len(cjj) == 1 and ch not in _JUNG_: #중성 아닐 때
first_ja.append(ch)
cjj = []
cjj.append(ch)
elif len(cjj) == 2 and ch in _JUNG_: #겹모음 (중성)
double_eng = jamo2engkey(cjj[1]+ch)
if double_eng in inv_dict:
double_jung = inv_dict[double_eng]
cjj[1] = double_jung
else:
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), 0))
first_mo.append(ch)
cjj = []
first_ja = []
elif len(cjj) == 2 and ch in _CHO_ and ch not in _JONG_: #무조건 초성
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), 0))
cjj = []
cjj.append(ch)
first_ja = []
first_ja.append(ch)
elif len(cjj) == 2 and ch in _JONG_: #초성 또는 종성 일단 종성간주
cjj.append(ch) #길이 3됨
elif len(cjj) == 3 and ch in _JUNG_: #이러면 종성이 아니라 초성
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), 0)) #cjj index 1까지만 res에
cho = cjj[2]
cjj = []
cjj.append(cho)
cjj.append(ch)
first_ja = []
elif len(cjj) == 3 and ch in _CHO_ and ch not in _JONG_: #무조건 초성
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), _JONG_.index(cjj[2])+1)) #결과로 넘기고(초, 중, 종) 다 존재
cjj = []
cjj.append(ch)
first_ja = []
first_ja.append(ch)
elif len(cjj) == 3 and ch in _JONG_: #일단 종성 간주 (종성이면 겹받침)
cjj.append(ch)
elif len(cjj) == 4 and ch in _JUNG_: #종성이 아니라 초성
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), _JONG_.index(cjj[2])+1)) #index 2까지만 res에
cho = cjj[3]
cjj = []
cjj.append(cho)
cjj.append(ch)
first_ja = []
elif len(cjj) == 4 and ch not in _JUNG_: #겹받침
double_eng = jamo2engkey(cjj[2]+ cjj[3])
first_ja = []
if double_eng in inv_dict:
double_jong = inv_dict[double_eng]
cjj[2] = double_jong
del cjj[3]
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), _JONG_.index(cjj[2])+1)) #res 저장
else:
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), _JONG_.index(cjj[2])+1))
first_ja.append(cjj[3])
cjj = []
cjj.append(ch)
first_ja.append(ch)
if len(first_mo) == 2:
double_eng = jamo2engkey(first_mo[0] + first_mo[1])
if double_eng in inv_dict:
double_jung = inv_dict[double_eng]
res.append(double_jung)
first_mo = []
else:
res.append(first_mo[0])
first_mo[0] = first_mo[1]
del first_mo[1]
if len(first_ja) == 3:
double_eng = jamo2engkey(first_ja[0] + first_ja[1])
if double_eng in inv_dict:
double_cho = inv_dict[double_eng]
res.append(double_cho)
first_ja[0] = first_ja[2]
del first_ja[2]
del first_ja[1]
else:
res.append(first_ja[0])
first_ja[0] = first_ja[1]
first_ja[1] = first_ja[2]
del first_ja[2]
else: #한글 아닌 경우
if len(cjj) == 1 and len(first_ja) == 0:
res.append(cjj[0])
if len(cjj) == 2:
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), 0))
elif len(cjj) == 3:
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), _JONG_.index(cjj[2])+1))
elif len(cjj) == 4:
double_eng = jamo2engkey(cjj[2]+ cjj[3])
if double_eng in inv_dict:
double_jong = inv_dict[double_eng]
cjj[2] = double_jong
del cjj[3]
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), _JONG_.index(cjj[2])+1)) #res 저장
else:
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), _JONG_.index(cjj[2])+1))
res.append(cjj[3])
cjj = []
cjj.append(ch)
if len(first_mo) == 2:
double_eng = jamo2engkey(first_mo[0] + first_mo[1])
if double_eng in inv_dict:
double_jung = inv_dict[double_eng]
res.append(double_jung)
else:
res.append(first_mo[0])
res.append(first_mo[1])
elif len(first_mo) == 1:
res.append(first_mo[0])
if len(first_ja) == 2:
double_eng = jamo2engkey(first_ja[0] + first_ja[1])
if double_eng in inv_dict:
double_cho = inv_dict[double_eng]
res.append(double_cho)
else:
res.append(first_ja[0])
res.append(first_ja[1])
elif len(first_ja) == 1:
res.append(first_ja[0])
res.append(ch)
cjj = []
first_mo = []
first_ja = []
if len(cjj) == 1 and len(first_ja) == 0:
res.append(cjj[0])
elif len(cjj) == 2:
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), 0))
elif len(cjj) == 3:
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), _JONG_.index(cjj[2])+1))
elif len(cjj) == 4:
double_eng = jamo2engkey(cjj[2]+ cjj[3])
if double_eng in inv_dict:
double_jong = inv_dict[double_eng]
cjj[2] = double_jong
del cjj[3]
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), _JONG_.index(cjj[2])+1)) #res 저장
else:
res.append(compose(_CHO_.index(cjj[0]), _JUNG_.index(cjj[1]), _JONG_.index(cjj[2])+1))
res.append(cjj[3])
cjj = []
cjj.append(ch)
if len(first_mo) == 2:
double_eng = jamo2engkey(first_mo[0] + first_mo[1])
if double_eng in inv_dict:
double_jung = inv_dict[double_eng]
res.append(double_jung)
else:
res.append(first_mo[0])
res.append(first_mo[1])
elif len(first_mo) == 1:
res.append(first_mo[0])
if len(first_ja) == 2:
double_eng = jamo2engkey(first_ja[0] + first_ja[1])
if double_eng in inv_dict:
double_cho = inv_dict[double_eng]
res.append(double_cho)
else:
res.append(first_ja[0])
res.append(first_ja[1])
elif len(first_ja) == 1:
res.append(first_ja[0])
return ''.join(res)
마지막 요소이자 가장 핵심이라고 볼 수 있는 jamo2syllable에 대해서 알아보자.
이 함수는 자음, 모음을 한국어 단어로 변환해주는 함수이다.
일단 기본적으로 코드가 엄청 길기 때문에 어떻게 구성되어 있는지를 중심으로 설명할 예정이다.
먼저 우리가 입력 받은 자음과 모음이 한글인지 아닌지 확인하는 작업이 필요하다.
만약 한글이라면, 주어진 자음과 모음을 이용해서 단어를 만들면 되는데 한 음절을 만드는 방법에는 총 4가지의 경우의 수가 있다.
먼저 첫 번째로 [아, 이, 우, 에, 오]와 같이 하나의 모음으로만 구성되어 있는 경우가 있고
두 번째로 [가, 차, 수, 거, 레]와 같이 자음이 먼저 나오고 다음으로 모음이 나오는 경우가 있고
세 번째로 [알, 임, 왕, 옆, 옛]과 같이 모음이 먼저 나오고 다음으로 자음이 나오는 경우가 있고
마지막 네 번째로 [강, 숲, 땅, 삶, 창]과 같이 자음, 모음, 자음으로 나오는 경우가 있다.
나는 이 네 가지 경우의 수를 기준으로 코드를 작성했다.
특히 [땅, 삶]과 같이 겹자음이나 쌍자음이 나오는 경우도 하나 하나 다 계산을 해야 한다.
def a(line):
line = line.rstrip()
# 문자열을 자모 문자열로 변환 ('닭고기' -> 'ㄷㅏㄺㄱㅗㄱㅣ')
jamo_str = str2jamo(line)
key_str = jamo2engkey(jamo_str)
jamo_str = engkey2jamo(key_str)
syllables = jamo2syllable(jamo_str)
return (syllables)
마지막으로 우리가 지금까지 만든 함수들을 하나의 함수에 합치면 완성이다.
우리가 흔히 영타를 한국어로 바꾸는 일이 많기도 하고 익숙하기 때문에 코드로 쉽게 구현이 가능할 것 같다고 생각했는데 생각보다 음절을 만드는 규칙이 다양하고 까다로워서 많이 애를 먹었다.
만약 나처럼 영타를 한국어로 바꾸는 일이 필요하면 이 코드를 그대로 사용하면 될 것 같다.