한글 초/중/종성 나누기: 키보드 기준
cho <- unlist(strsplit("ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ", ""))
jung <- unlist(strsplit("ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ", ""))
intToUtf8v <- Vectorize(intToUtf8)
#jong2 <- c(" ", intToUtf8v(4520:(4520+26))) # 4520 = ㄱ
#jong <- unlist(strsplit(" /ㄱ/ㄲ/ㄱㅅ/ㄴ/ㄴㅈ/ㄴㅎ/ㄷ/ㄹ/ㄹㄱ/ㄹㅁ/ㄹㅂ/ㄹㅅ/ㄹㅌ/ㄹㅍ/ㄹㅎ/ㅁ/ㅂ/ㅂㅅ/ㅅ/ㅆ/ㅇ/ㅈ/ㅊ/ㅋ/ㅌ/ㅍ/ㅎ", "/"))
jong <- unlist(strsplit("ㄱ/ㄲ/ㄱㅅ/ㄴ/ㄴㅈ/ㄴㅎ/ㄷ/ㄹ/ㄹㄱ/ㄹㅁ/ㄹㅂ/ㄹㅅ/ㄹㅌ/ㄹㅍ/ㄹㅎ/ㅁ/ㅂ/ㅂㅅ/ㅅ/ㅆ/ㅇ/ㅈ/ㅊ/ㅋ/ㅌ/ㅍ/ㅎ", "/"))
jong2 <- intToUtf8v(4520:(4520+26)) # 4520 = ㄱ
한글의 초/중/종성을 분리하여 문장 사이 거리 구하기
- (Edit-based) Approximate Matching을 위해서는 한글 초,중,종성을 분리해야 한다.
- 그래서 인터넷을 찾아봤는데 소득이 별로 없었다.
- 그래서 RCppMecab 패키지의 관리자님에게 물어보았다.
“간단합니다.”
- 정말 간단했다. 의외로 ‘UTF-8’은 규칙적으로 Encoding이 되어 있었다. 다음 코드는 김주혁님의 코드를 약간 수정한 것이다.
decompose <- function(x) {
if (Encoding(x) != "UTF-8") {
x <- iconv(x, from="", to='UTF-8')
}
#if (x < "\uac00" || x > "\ud7af") return(cat(x))
if (x < "\uac00" || x > "\ud7a3") return(x)
x <- utf8ToInt(x) - 44032
y <- x %/% 28
z <- x %% 28
x <- y %/% 21
y <- y %% 21
zz <- jong[z]
return(c(cho[x + 1],jung[y + 1],zz))
}
decompose("갔")
## [1] "ㄱ" "ㅏ" "ㅆ"
decompose("나")
## [1] "ㄴ" "ㅏ"
decomposeV <- Vectorize(decompose)
unlist(decomposeV(unlist(strsplit('나는 NICAR conference Review에 갔다. 유익한 시간이었다. Snowfall이 유명하다더라.',""))))
## 나1 나2 는1 는2 는3 N I C A R c o n ## "ㄴ" "ㅏ" "ㄴ" "ㅡ" "ㄴ" " " "N" "I" "C" "A" "R" " " "c" "o" "n" ## f e r e n c e R e v i e w 에1 ## "f" "e" "r" "e" "n" "c" "e" " " "R" "e" "v" "i" "e" "w" "ㅇ" ## 에2 갔1 갔2 갔3 다1 다2 . 유1 유2 익1 익2 익3 한1 ## "ㅔ" " " "ㄱ" "ㅏ" "ㅆ" "ㄷ" "ㅏ" "." " " "ㅇ" "ㅠ" "ㅇ" "ㅣ" "ㄱ" "ㅎ" ## 한2 한3 시1 시2 간1 간2 간3 이1 이2 었1 었2 었3 다1 다2 ## "ㅏ" "ㄴ" " " "ㅅ" "ㅣ" "ㄱ" "ㅏ" "ㄴ" "ㅇ" "ㅣ" "ㅇ" "ㅓ" "ㅆ" "ㄷ" "ㅏ" ## . S n o w f a l l 이1 이2 유1 유2 ## "." " " "S" "n" "o" "w" "f" "a" "l" "l" "ㅇ" "ㅣ" " " "ㅇ" "ㅠ" ## 명1 명2 명3 하1 하2 다1 다2 더1 더2 라1 라2 . ## "ㅁ" "ㅕ" "ㅇ" "ㅎ" "ㅏ" "ㄷ" "ㅏ" "ㄷ" "ㅓ" "ㄹ" "ㅏ" "."
decomp <- function(x) {
stopifnot(class(x) == "character")
paste0(unlist(decomposeV(unlist(strsplit(x,"")))), collapse='')
}
(x1 <- decomp('나는 NICAR Conference Review에 갔다. 유익한 시간이었다. Snowfall이 유명하다더라.'))
## [1] "ㄴㅏㄴㅡㄴ NICAR Conference Reviewㅇㅔ ㄱㅏㅆㄷㅏ. ㅇㅠㅇㅣㄱㅎㅏㄴ ㅅㅣㄱㅏㄴㅇㅣㅇㅓㅆㄷㅏ. Snowfallㅇㅣ ㅇㅠㅁㅕㅇㅎㅏㄷㅏㄷㅓㄹㅏ."
(x2 <- decomp('나는 NICAR conference Review에 갔다. 유익한 시간이ㅓㅆ다. Snowfall이 유명하다더라.'))
## [1] "ㄴㅏㄴㅡㄴ NICAR conference Reviewㅇㅔ ㄱㅏㅆㄷㅏ. ㅇㅠㅇㅣㄱㅎㅏㄴ ㅅㅣㄱㅏㄴㅇㅣㅓㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎㅚㅍㄷㅏ. Snowfallㅇㅣ ㅇㅠㅁㅕㅇㅎㅏㄷㅏㄷㅓㄹㅏ."
(x3 <- decomp('나는 NidAR conference Review에 갔다. 유익한 시간이었다. Snowfall이 유명하다더라.'))
## [1] "ㄴㅏㄴㅡㄴ NidAR conference Reviewㅇㅔ ㄱㅏㅆㄷㅏ. ㅇㅠㅇㅣㄱㅎㅏㄴ ㅅㅣㄱㅏㄴㅇㅣㅇㅓㅆㄷㅏ. Snowfallㅇㅣ ㅇㅠㅁㅕㅇㅎㅏㄷㅏㄷㅓㄹㅏ."
- (edit-based) Approximate matching을 해보자. 먼저
stringdist
를 불러들이고,
require(stringdist)
## Loading required package: stringdist
stringdist(x1, x2, method='dl')
## [1] 23
stringdist(x1, x3, method='dl')
## [1] 3
- 앗, 거리차이가 이렇게나? 다시 위의 분해된 결과를 보니 뭔가 이상하다.
- 몇 가지 조사를 해 본 결과,
'ㄱ'
,'ㄴ'
등은 위의 계산식에 부합하지 않는다.
library(dplyr)
'ㄱ' %>% iconv(to='UTF-8') -> x
utf8ToInt(x)
## [1] 12593
'ㅎ' %>% iconv(to='UTF-8') -> x
utf8ToInt(x)
## [1] 12622
12593:12622 %>% intToUtf8v
## [1] "ㄱ" "ㄲ" "ㄳ" "ㄴ" "ㄵ" "ㄶ" "ㄷ" "ㄸ" "ㄹ" "ㄺ" "ㄻ" "ㄼ" "ㄽ" "ㄾ" ## [15] "ㄿ" "ㅀ" "ㅁ" "ㅂ" "ㅃ" "ㅄ" "ㅅ" "ㅆ" "ㅇ" "ㅈ" "ㅉ" "ㅊ" "ㅋ" "ㅌ" ## [29] "ㅍ" "ㅎ"
decompose <- function(x) {
if (Encoding(x) != "UTF-8") {
x <- iconv(x, from="", to='UTF-8')
}
#if (x < "\uac00" || x > "\ud7af") return(cat(x))
if (x < "\uac00" || x > "\ud7a3") return(x)
if (utf8ToInt(x) >= 12593 && utf8ToInt(x) <= 12622) return(x)
x <- utf8ToInt(x) - 44032
y <- x %/% 28
z <- x %% 28
x <- y %/% 21
y <- y %% 21
zz <- jong[z]
return(c(cho[x + 1],jung[y + 1],zz))
}
decomposeV <- Vectorize(decompose)
decomp <- function(x) {
stopifnot(class(x) == "character")
paste0(unlist(decomposeV(unlist(strsplit(x,"")))), collapse='')
}
(x1 <- decomp('나는 NICAR Review에 갔다. 유익한 시간이었다. '))
## [1] "ㄴㅏㄴㅡㄴ NICAR Reviewㅇㅔ ㄱㅏㅆㄷㅏ. ㅇㅠㅇㅣㄱㅎㅏㄴ ㅅㅣㄱㅏㄴㅇㅣㅇㅓㅆㄷㅏ. "
(x2 <- decomp('나는 NICAR Review에 갔다. 유익한 시간이ㅓㅆ다. '))
## [1] "ㄴㅏㄴㅡㄴ NICAR Reviewㅇㅔ ㄱㅏㅆㄷㅏ. ㅇㅠㅇㅣㄱㅎㅏㄴ ㅅㅣㄱㅏㄴㅇㅣㅓㅆㄷㅏ. "
(x3 <- decomp('나느ㅣ NidAR Review에 갔다. 유익한 시간이었다. '))
## [1] "ㄴㅏㄴㅡㅣ NidAR Reviewㅇㅔ ㄱㅏㅆㄷㅏ. ㅇㅠㅇㅣㄱㅎㅏㄴ ㅅㅣㄱㅏㄴㅇㅣㅇㅓㅆㄷㅏ. "
- 이제 다시 (edit-based) Approximate matching을 해보자.
require(stringdist)
stringdist(x1, x2, method='dl')
## [1] 1
stringdist(x1, x3, method='dl')
## [1] 4
- 이제야 결과가 제대로 나왔다.
x2
는ㅇ
을 치지 않는 것이고(delete)x3
는ㄴ
을 치지 않고(delete),ㅣ
가 추가(insert) 되었다.
x2
와x3
를 보면,x1
과 크게 다른 듯 보이지만, 단 한 두개의 키만 생략하거나 추가한 결과라는 것을 확인할 수 있었다.
Approximate matching을 위한 edit-based distance
stringdist::stringdist(a, b, method= )
method=
'osa'
'lv'
: delete, insert, substitution'dl'
: delete, insert, substitution, transposition'hamming'
'lcs'
: delete, insert'gram'
'cosine'
'jaccard'
'jw'
'soundex'
세 줄 코드 정리
# x1, x2 : 거리를 구할 문자열
x1 <- "좋아!"
x2 <- "조하?"
stringdist::stringdist(decomp(x1), decomp(x2), "dl")
## [1] 2
- 참고자료 : 유니코드 차트
- 참고로 한글 초/중/종성 분리는 패키지
KoNLP
에서도 지원을 하지만 edit-based 거리를 구하는 데에는 적합하지 않았다. (속도는 훨씬 빠르긴 하지만…)
Leave a comment