# 자모 리스트
= [
CHOSUNG_LIST 'ㄱ','ㄲ','ㄴ','ㄷ','ㄸ','ㄹ','ㅁ','ㅂ','ㅃ','ㅅ','ㅆ',
'ㅇ','ㅈ','ㅉ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ'
]= [
JUNGSUNG_LIST 'ㅏ','ㅐ','ㅑ','ㅒ','ㅓ','ㅔ','ㅕ','ㅖ','ㅗ','ㅘ','ㅙ','ㅚ',
'ㅛ','ㅜ','ㅝ','ㅞ','ㅟ','ㅠ','ㅡ','ㅢ','ㅣ'
]= [
JONGSUNG_LIST '', 'ㄱ','ㄲ','ㄳ','ㄴ','ㄵ','ㄶ','ㄷ','ㄹ','ㄺ','ㄻ','ㄼ','ㄽ','ㄾ','ㄿ','ㅀ',
'ㅁ','ㅂ','ㅄ','ㅅ','ㅆ','ㅇ','ㅈ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ'
]
= [
DOUBLE_JONG_LIST 'ㄱ', 'ㅅ', 'ㄳ'),
('ㄴ', 'ㅈ', 'ㄵ'),
('ㄴ', 'ㅎ', 'ㄶ'),
('ㄹ', 'ㄱ', 'ㄺ'),
('ㄹ', 'ㅁ', 'ㄻ'),
('ㄹ', 'ㅂ', 'ㄼ'),
('ㄹ', 'ㅅ', 'ㄽ'),
('ㄹ', 'ㅌ', 'ㄾ'),
('ㄹ', 'ㅍ', 'ㄿ'),
('ㄹ', 'ㅎ', 'ㅀ'),
('ㅂ', 'ㅅ', 'ㅄ')
(
]
= [
DOUBLE_JUNG_LIST 'ㅗ', 'ㅏ', 'ㅘ'),
('ㅗ', 'ㅐ', 'ㅙ'),
('ㅗ', 'ㅣ', 'ㅚ'),
('ㅜ', 'ㅓ', 'ㅝ'),
('ㅜ', 'ㅔ', 'ㅞ'),
('ㅜ', 'ㅣ', 'ㅟ'),
('ㅡ', 'ㅣ', 'ㅢ')
(
]
# 상태 초기화
def reset_state():
return {
'output': [],
'cho': '',
'jung': '',
'jong': '',
'state': '초성'
}
# 글자 조합
def combine_jamos(cho, jung, jong=''):
= CHOSUNG_LIST.index(cho)
cho_idx = JUNGSUNG_LIST.index(jung)
jung_idx = JONGSUNG_LIST.index(jong) if jong else 0
jong_idx return chr(0xAC00 + 588 * cho_idx + 28 * jung_idx + jong_idx)
# 이중 중성 처리
def merge_double_jung(j1, j2):
return DOUBLE_JUNG.get((j1, j2), None)
# 이중 종성 처리
def normalize_jong(jong):
if jong in DOUBLE_JONG:
return DOUBLE_JONG[jong]
return jong
# 글자 출력 및 상태 초기화
def flush(state):
= ''
result
if state['cho'] and state['jung']:
= normalize_jong(state['jong']) if state['jong'] else ''
jong = combine_jamos(state['cho'], state['jung'], jong)
result elif state['jung']:
= state['jung']
result elif state['cho']:
= state['cho']
result
# 상태 초기화
'cho'] = ''
state['jung'] = ''
state['jong'] = ''
state['state'] = '초성'
state[
return result
# 숫자인지 확인
def is_number(char):
return char.isdigit()
def is_hangul_syllable(char):
return 0xAC00 <= ord(char) <= 0xD7A3
def decompose(syllable):
if not is_hangul_syllable(syllable):
return syllable, '', ''
= ord(syllable) - 0xAC00
code = code // 588
cho = (code % 588) // 28
jung = code % 28
jong return CHOSUNG_LIST[cho], JUNGSUNG_LIST[jung], JONGSUNG_LIST[jong]
def compose(cho, jung, jong=''):
if cho not in CHOSUNG_LIST or jung not in JUNGSUNG_LIST:
return cho + jung + jong # 비정상 조합은 그냥 이어 붙임
= CHOSUNG_LIST.index(cho)
cho_index = JUNGSUNG_LIST.index(jung)
jung_index = JONGSUNG_LIST.index(jong) if jong in JONGSUNG_LIST else 0
jong_index
= 0xAC00 + (cho_index * 588) + (jung_index * 28) + jong_index
code return chr(code)
= {(a , b): c for a, b, c in DOUBLE_JUNG_LIST}
DOUBLE_JUNG = {a + b: c for a, b, c in DOUBLE_JONG_LIST}
DOUBLE_JONG
# 역조합용 딕셔너리 (백스페이스 처리에 사용)
= {c: (a, b) for a, b, c in DOUBLE_JUNG_LIST}
REVERSE_DOUBLE_JUNG # 역조합을 이용해서 이중 중성을 분해한다.
= {c: (a, b) for a, b, c in DOUBLE_JONG_LIST}
REVERSE_DOUBLE_JONG # 역조합을 이용해서 이중 종성을 분해한다.
Hangeul Automata
Hangeul Automata
1. 목표
한글 자소, 숫자, 백스페이스를 입력받아, 완성형 문장을 만드는 오토마타를 구현
2. 설명
state
: 상태
char
: 입력을 자소 단위로 쪼갠 것
ex) ['ㄱ','ㅏ','ㄴ','ㅏ','ㄷ','ㅣ','1','<']
가 input이라면 'ㄱ','ㅏ', ... , '<'
이 char이고 'ㄱ'
을 입력받았을 때 state
는 중성(모음)을 입력받길 기대하므로 state = 중성
이다.
ㅁㅏㄴ
(순으로 키보드를 누르면 “만”이 완성되고, ㅁㅏㄴㅏ
순으로 키보드를 누르면 “마나”가 완성)
초성 입력 -> 중성 -> 종성 이후에 자음이 오는지 모음이 오는지에 따라서 입력된 종성의 state가 다르게 결정된다!!
핵심
1. state에서 종성보다는 종성 후보가 적합하다. 왜냐하면 자음이 들어온 그 순간에는 그 자음이 종성인지, 새로운 음절의 초성인지 판단할 수 없기 때문이다.
2. 다음 입력을 보기 위해서 next_char이 필요하다
3. 경우의 수
state = 초성
- 입력: 자음 -> 초성 저장
- 입력: 모음 -> 다음 입력까지 보고 이중모음이면 merge, 아니면 모음 출력
state = 중성
- 입력: 자음 -> 이전 내용 출력, 초성 저장
- 입력: 모음 -> 다음 입력까지 보고 이중모음이면 merge, 아니면 결합하여 출력
state = 종성후보
- 입력: 자음 + 자음 -> 이중자음이면 merge, 아니라면 첫 번째 자음은 종성, 두 번째 자음은 새로운 초성
- 입력: 자음 + 모음 -> 자음은 종성이 되어서 출력, 모음은 그냥 모음 출력
- 입력: 모음 + 모음 -> 이중모음이면 merge 후 출력, 아니라면 따로따로 출력
- 입력: 모음 + 자음 -> 모음이기에 이전 내용 및 모음 출력, 자음은 새로운 글자의 초성
- 입력: 모음 -> 모음이기에 이전 내용 및 모음 출력
- 입력: 자음 -> 종성이 되고 출력
숫자 구현 1. 숫자는 한글과 절대 결합이 불가능하므로 그냥 이어붙히기
지우기 구현 1. 종성이 있는지 확인 -> 이중 종성이라면 분리해서 뒷글자 삭제, 아니라면 그냥 삭제 2. 종성이 없으면 중성이 있는지 확인 -> 이중 중성이라면 분리하여 뒷 글자 삭제, 아니라면 그냥 삭제 3. 중성도 없으면 초성이 있는지 확인 -> 있으면 초성 삭제 4. 만약 숫자라면 그냥 삭제
4. 구현
-
자모 및 이중모음, 이중 자음, 함수 선언
# 전체 오토마타
def automata(inputs):
= reset_state()
state
= 0
i while i < len(inputs):
= inputs[i]
char = inputs[i + 1] if i + 1 < len(inputs) else None
next_char
if char == '<': # 백스페이스 처리
if state['jong']:
# 이중 종성일 경우
if state['jong'] in REVERSE_DOUBLE_JONG:
= REVERSE_DOUBLE_JONG[state['jong']]
first, second 'jong'] = first # 첫 자음만 남기고 나머지 삭제
state[else:
'jong'] = ''
state[elif state['jung']:
# 이중 모음일 경우
if state['jung'] in REVERSE_DOUBLE_JUNG:
= REVERSE_DOUBLE_JUNG[state['jung']]
first, second 'jung'] = first # 첫 모음만 남기고 나머지 삭제
state[else:
'jung'] = ''
state[elif state['cho']:
'cho'] = ''
state[elif state['output']:
= state['output'].pop()
last if last.isdigit():
pass
elif is_hangul_syllable(last):
= decompose(last)
cho, jung, jong if jong:
# 이중 종성인지 확인
if jong in REVERSE_DOUBLE_JONG:
= REVERSE_DOUBLE_JONG[jong]
first, second 'cho'] = cho
state['jung'] = jung
state['jong'] = first
state[else:
'cho'] = cho
state['jung'] = jung
state['jong'] = ''
state[else:
# 종성이 없으면 중성 삭제
if jung in REVERSE_DOUBLE_JUNG:
= REVERSE_DOUBLE_JUNG[jung]
first, _ 'cho'] = cho
state['jung'] = first
state['jong'] = ''
state[else:
'cho'] = cho
state['jung'] = ''
state['jong'] = ''
state[+= 1
i continue
if is_number(char):
= flush(state)
flushed if flushed:
'output'].append(flushed)
state['output'].append(char)
state[+= 1
i continue
if state['state'] == '초성':
# 1. 상태가 초성일 때 자음(= 초성)이 입력되는 경우
if char in CHOSUNG_LIST:
'cho'] = char
state['state'] = '중성'
state[+= 1
i elif char in JUNGSUNG_LIST:
# 2. 상태가 초성일 때 모음이 먼저 입력되는 경우 중 merged 되는 경우
if next_char in JUNGSUNG_LIST: # char: 모음 next_char: 모음
= merge_double_jung(char, next_char)
merged if merged:
'jung'] = merged
state['state'] = '종성후보'
state[+= 2
i continue
else:
# 3. merged 되지 않는 경우
'jung'] = char
state['state'] = '종성후보'
state[+= 1
i else: # 4. char과 next char 모두 모음이 아닌 경우
'jung'] = char
state[= flush(state)
flushed if flushed:
'output'].append(flushed)
state['state'] = '초성'
state[+= 1
i continue
elif state['state'] == '중성':
if char in CHOSUNG_LIST:
# 1. 자음이 들어오면 이전 것 flush 후 초성으로 저장
= flush(state)
flushed if flushed:
'output'].append(flushed)
state['cho'] = char
state['state'] = '중성'
state[+= 1
i
elif char in JUNGSUNG_LIST:
if next_char in JUNGSUNG_LIST:
# 2. 이중 모음일 수 있음
= merge_double_jung(char, next_char)
merged if merged:
'jung'] = merged
state['state'] = '종성후보'
state[+= 2
i continue
# 3. 이중 모음이 아님
'jung'] = char
state['state'] = '종성후보'
state[+= 1
i
elif state['state'] == '종성후보':
# 1. char과 next_char가 모두 자음일 때
if char in JONGSUNG_LIST and next_char in JONGSUNG_LIST:
= normalize_jong(char + next_char)
double_jong if double_jong in JONGSUNG_LIST:
'jong'] = double_jong
state[= flush(state)
flushed if flushed:
'output'].append(flushed)
state['state'] = '초성'
state[+= 2
i else:
# 2. 이중자음 불가 → char은 종성, next_char는 초성
'jong'] = char
state[= flush(state)
flushed if flushed:
'output'].append(flushed)
state['state'] = '초성'
state[+= 1
i
# 3. char은 자음이고 next_char는 모음 → char은 초성
elif char in JONGSUNG_LIST and next_char in JUNGSUNG_LIST:
= flush(state)
flushed if flushed:
'output'].append(flushed)
state['cho'] = char
state['state'] = '중성'
state[+= 1
i
# 4. char과 next_char가 모두 모음일 때
elif char in JUNGSUNG_LIST and next_char in JUNGSUNG_LIST:
= merge_double_jung(char, next_char)
merged if merged:
= flush(state)
flushed if flushed:
'output'].append(flushed)
state['jung'] = merged
state[= flush(state)
flushed if flushed:
'output'].append(flushed)
state['state'] = '초성'
state[+= 2
i else:
# 5. char이 모음이고 next char도 모음이지만 이중모음이 안될때
= flush(state)
flushed if flushed:
'output'].append(flushed)
state['jung'] = char
state[+= 1
i = flush(state)
flushed if flushed:
'output'].append(flushed)
state['state'] = '초성'
state[
# 6. char이 모음이고 next_char는 자음일 때
elif char in JUNGSUNG_LIST and next_char in CHOSUNG_LIST:
= flush(state)
flushed if flushed:
'output'].append(flushed)
state['jung'] = char
state[+= 1
i = flush(state)
flushed if flushed:
'output'].append(flushed)
state['state'] = '초성'
state[# 7. char이 단독 모음일 때,
elif char in JUNGSUNG_LIST:
# 예외 처리 또는 단독 출력
= flush(state)
flushed if flushed:
'output'].append(flushed)
state['jung'] = char
state[= flush(state)
flushed if flushed:
'output'].append(flushed)
state['state'] = '초성'
state[+= 1
i # 8. char이 단독 자음일 때,
elif char in CHOSUNG_LIST:
'jong'] = char
state[= flush(state)
flushed if flushed:
'output'].append(flushed)
state['state'] = '초성'
state[+= 1
i
= flush(state)
flushed if flushed:
'output'].append(flushed)
state[
return ''.join(state['output'])
6. Test
= 'ㅇㅏㄴㄴㅕㅇㅎㅏㅅㅔㅇㅛ123<'
inp print(automata(inp))
안녕하세요12