본문 바로가기

테크 노트/소소한 개발 팁

한글 유니코드를 초성, 중성, 종성으로 쪼개자

2019/03/18 - [개발이/아이디어노트] - 스팸체 생성기

앞서 스팸체 생성기에 대해 소개했었는데요.
관련하여 가장 중요한 내용을 여기에 써보려합니다.

바로 한글을 분리하고 조합하는 내용입니다.
일단 유니코드 한글영역에 대해 알아야 합니다.

 

유니코드?

다들 아시겠지만, 혹시 더 궁금하시면 아래 링크를 참고해주세요.
이 포스팅에서 제가 다루진 않겠습니다.
https://ko.wikipedia.org/wiki/유니코드

 

유니코드 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 다른 뜻에 대해서는 U;Nee Code 문서를 참조하십시오. 유니코드(Unicode)는 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준이며, 유니코드 협회(Unicode Consortium)가 제정한다. 이 표준에는 ISO 10646 문자 집합, 문자 인코딩, 문자 정보 데이터베이스, 문자들을 다루기 위한 알고리즘 등을 포함하고 있다. 유니코드의 목적은 현존하는 문자 인코딩 방법들을

ko.wikipedia.org

 

스팸체 생성기 예시

규칙  예시
가능한 경우 한글을 가로로 쪼개자. 스위치 → 스우ㅣㅊㅣ
쪼개진 자모를 특문으로 바꾸자. ㄱㅏㄴㅏ → 7r ㄴr
초성, 종성을 복잡하게 바꾸자. 치킨먹자 → 칧킪먻짞
공백을 특수문자로 채우자. 연결 고리 → 연결✦고리
공백이 아니어도 특정 확률로 특문을 넣자. 동해물과백두 → 동해물✿과❅백♛➳두
영문자를 전각으로 바꾸자. abc → abc
숫자를 전각으로 바꾸거나 특문으로 바꾸자. 123 → 丨己彐
위를 다 하고도 바꿀 수 있다면 바꾸자. 스고 → 亼卫

여기서 유니코드 한글 영역에 대한 이해가 필요한 부분은 바로 1, 2, 3번 항목입니다.
이제 저 세 항목을 어떻게 다룰지 생각해봅시다.

 

유니코드 한글 영역

유니코드 한글 영역을 알아야 합니다.
이 코드를 가지고 한글을 조합, 분해하며 한글을 망가트려야 하기 때문이죠.
http://www.unicode.org/charts/ 에서 우리가 필요한 한글 정보를 얻을 수 있는데요.
우리가 참고할 부분은 바로 Hangul Jamo, Hangle Syllables입니다.

Hangul Jamo는 자음, 모음 영역입니다. (0x1100 ~ 0x11FF)
쌍니은, 쌍이응, ㅛㅑ 등의 옛 자음, 모음도 포함되어있습니다만
우리는 아래의 문자만 사용하게 됩니다.

  • 초성(19)
    'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
  • 중성(21)
    'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ'
  • 중성(28)
    없음, 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'

Hangle Syllables 은 한글 음절 영역입니다.
'가'에서 '힣' 까지입니다. (0xAC00 ~ 0xDCAF)
위 초성, 중성, 종성의 순서대로 조합되어 있습니다.

 


 

초성 19개, 중성 21개, 종성 28개이고, 위에 나열된 순서대로 조합되어 있으므로
한글 한 음절은 아래와 같은 공식을 따릅니다.

# 각 초성, 중성, 종성의 순서는 0부터 시작합니다

한글 문자 코드
= (초성 순서 * 21 * 28) + (중성 순서 * 28) + 종성 순서 + 0xAC00
= ((초성 순서 * 21) + 중성 순서) * 28 + 종성 순서 + 0xAC00

예를 들어 봅시다.
첫 글자인 '가'의 경우, 초성 1번째, 중성 1번째, 종성 1번째입니다.
따라서 (0 * 21 * 28) + (0 * 28) + 0 = 0입니다.
0xAC00 부터 0번째 뒤에 있는 문자인 것입니다.

그렇다면 0xAC01과 0xAC02는 무엇일까요?
1씩 늘어났으므로 종성의 순서가 증가되었음을 알 수 있습니다.
따라서 각각 '각', '갂' 이 되겠죠.

그럼 '나'의 경우는?
'ㄴ'은 세 번째 초성이며 중성, 종성은 첫 번째이므로
(2 * 21 * 28) + (0 * 28) + 0 =  1176
즉, '가'로부터 1176번째 뒤에 있는 값입니다.

따라서 '나' 코드값은 아래와 같습니다.
0xAC00(16) + 1176(10)
= 44032(10) + 1176(10)
= 45208(10) = 0xB098(16)

 


 

이제 분리를 해봅시다. 복잡하니까 0xAC00 을 제외한 "한글 문자 순서"만 고려해보죠.

한글 문자 순서 = 한글 문자 코드 - 0xAC00

앞서 보듯이 중성은 21개, 종성은 28개입니다.
각 문자가 21 * 28 번째마다 같은 중성, 종성을 갖되 "초성 순서"만 달라진다는 뜻이지요.
따라서 이렇게 "초성 순서"를 구할 수 있습니다.

초성 순서 = 한글 문자 순서 / (21 * 28)

이제 초성을 구하고 남은 나머지를 같은 방법으로 적용하면 중성을 구할 수 있습니다.
거기서 남은 나머지를 이용하면 종성을 구할 수 있고요.

따라서 조합 / 분리의 전체 공식은 아래와 같이 표현할 수 있습니다.

한글 문자 순서 = ((초성 순서 * 21) + 중성 순서) * 28 + 종성 순서
한글 문자 코드 = 조합된 한글 순서 + 0xAC00
초성 순서 = 한글 문자 순서 / (21 * 28)
중성 순서 = (한글 문자 순서 % (21 * 28)) / 28
종성 순서 = (한글 문자 순서 % (21 * 28)) % 28

 


 

그런데, 뭔가 부족합니다.

왢 -> ㅇ, ㅙ, ㄶ

우리가 구현한 것은 이렇게까지만 분리됩니다.

  • 초성, 종성을 복잡하게 바꾸자.
    예) 치킨먹자 → 칧킪먻짞

고안한 8가지 규칙 중 위를 구현 하려면
아래와 같이 더 잘게 쪼개야 합니다.

왢 -> {ㅇ}, {ㅗ, ㅐ}, {ㄴ,ㅎ}  

그리고 이렇게 쪼개려면 다른 조치가 필요하지요.
(참고로 ㅐ를 ㅏㅣ 까지 분리하진 않겠습니다.)

갑자기 머리가 아파옵니다만.. 사실 간단합니다.
위에 만든 초성, 중성, 종성 배열을 아래처럼 하나 더 만들면 됩니다.

  • 초성(19)
    ['ㄱ'], ['ㄱ', 'ㄱ'], ['ㄴ'], ['ㄷ'], ['ㄷ', 'ㄷ'], ['ㄹ'], ['ㅁ'], ['ㅂ'], ['ㅂ', ㅂ'], ['ㅅ'], ['ㅅ', 'ㅅ'], ['ㅇ'], ['ㅈ'], ['ㅈ', 'ㅈ'], ['ㅊ'], ['ㅋ'], ['ㅌ'], ['ㅍ'], ['ㅎ']
  • 중성(21)
    ['ㅏ'], ['ㅐ'], ['ㅑ'], ['ㅒ'], ['ㅓ'], ['ㅔ'], ['ㅕ'], ['ㅖ'], ['ㅗ'], ['ㅘ'], ['ㅙ'], ['ㅚ'], ['ㅛ'], ['ㅜ'], ['ㅜ', 'ㅓ'], ['ㅜ', 'ㅔ'], ['ㅜ', 'ㅣ'], ['ㅠ'], ['ㅡ'], ['ㅡ', 'ㅣ'], ['ㅣ']
  • 중성(28)
    없음, ['ㄱ'], ['ㄱ', 'ㄱ'], ['ㄱ', 'ㅅ'], ['ㄴ'], ['ㄴ', 'ㅈ'], ['ㄴ', 'ㅎ'], ['ㄷ'], ['ㄹ'], ['ㄹ', 'ㄱ'], ['ㄹ', 'ㅁ'], ['ㄹ', 'ㅂ'], ['ㄹ', 'ㅅ'], ['ㄹ', 'ㅌ'], ['ㄹ', 'ㅍ'], ['ㄹ', 'ㅎ'], ['ㅁ'], ['ㅂ'], ['ㅂ', 'ㅅ'], ['ㅅ'], ['ㅅ', 'ㅅ'], ['ㅇ'], ['ㅈ'], ['ㅊ'], ['ㅋ'],['ㅌ'], ['ㅍ'], ['ㅎ']

앞서 만든 데이터와 거의 같습니다.
이렇게 되면 배열의 한 수준이 더 생기긴 하지만
위에서 도출한 분리 공식을 똑같이 사용할 수 있습니다.

 

마치며

장난 치려고 한건데 알아야할게 꽤  많아져버렸습니다.
부디 저처럼 말고 좋은 곳에 참고하시길 바랍니다.

(예전에 브런치에 썼었던 글인데요. 이쪽으로 가져왔습니다. )