유니코드(Unicode)에 정의된 한글은 기본적으로 완성형을 따른다. 다시 말해서 초성/중성/종성이 합쳐져 있는 글자 하나씩을 코드셋(codeset)에 저장한 것이다.
그러나 다행히도 예전의 KSC5601 완성형이나 MS가 정의한 확장완성형(UHC, CP949)과는 달리 조합의 원칙에 따라 인코딩되어 있으므로 첫가끝(초성/중성/종성) 낱자, 다시 말해 한글 자소로 분해할 수 있다.
인터넷에서 찾아보니 이 기능을 위해 여러가지 꽁수나 매핑 테이블을 이용해서 구현해놓은 것들이 많은데, ICU 프로젝트에서 제공하고 있는 라이브러리를 사용하면 아주 간단하게 자소로 분해하는 기능을 얻을 수 있다.
#include <iostream>
#include <locale>
#include <cwchar>
#include <cstring>
#include "unistr.h"
#include "normlzr.h"
#include "unorm.h"
using namespace std;
int decompose(const char* text, wchar_t* wcs_buf, uint buf_size)
{
// UTF-8 to UCS4
UnicodeString str = UnicodeString::fromUTF8(StringPiece(text));
// UCS4 to NFD
UnicodeString result;
UErrorCode status = U_ZERO_ERROR;
Normalizer::normalize(str, UNORM_NFD, 0, result, status);
if (U_FAILURE(status)) {
cerr << "can't decompose a UTF8 string, "
<< status << ": " << u_errorName(status) << endl;
return -1;
}
result.toUTF32((UChar32*) wcs_buf, buf_size, status);
return 0;
}
int main(void)
{
const char* text = "한글";
wchar_t wcs_buf[1024];
decompose(text, wcs_buf, sizeof (wcs_buf));
for (uint i = 0; wcs_buf[i] != 0; ++i) {
cout << "wcs_buf[" << i << "]=0x" << hex << (int) wcs_buf[i] << endl;
}
cout << endl;
return 0;
}
ICU project에서 ICU4C 라이브러리를 다운로드한다.
/home/terzeron을 라이브러리 설치 디렉토리로 가정한다.
configure --prefix=/home/terzeron && make all install
g++ -c comp_test.cc -I/home/terzeron/include -I/home/terzeron/include/unicode -g -Wall
g++ -o comp_test.cc -L/home/terzeron/lib -licuuc -licudata comp_test.o
./comp_test
낱자를 구하는 것이 무슨 의미가 있을까? 낱자를 구하게 되면 보다 정확한 한글 단어 간의 거리(edit distance)를 구할 수 있고, 단어 간 거리를 알면 오타/정자 변환이 좀 더 용이해지며, 단어들을 클러스터링하기도 쉬워진다.
참고: 류창우님의 "첫가끝 인코딩을 사용하는 이유"
UTF-8 문자열 바이트 스트림이나 UCS4 문자열 바이트 스트림을 가지고 비교해도 단어 간 거리를 구할 수 있으나 자소 단위의 미묘한 차이를 발견하는 게 아니라 글자 단위의 거리가 되므로 이 방법은 자소를 이용한 거리계산 방법에 비해 부정확할 수 밖에 없다. 그러나 이 자소를 이용한 방법이 100% 정확한 것은 아니다. 자소 뿐만이 아니라 두벌식 자판이나 세벌식 자판에서 키 배치에 따라 자소 간의 거리를 따로 정의함으로써 보다 정확한 오타/정자용 거리를 계산할 수도 있다. 그러나 자소를 이용한 방법으로도 충분한 결과를 얻을 수 있다.
이전 글 한글 검색어 간 유사도를 위한 Levenshtein distance 함수한글_검색어_간_유사도를_위한_Levenshtein_distance_함수를 참고하면 자소 단위의 한글 단어 간 거리(유사도)를 계산할 수도 있다.