はじめに
gojaconvというGo用の文字列変換ライブラリを作成しました。
かな文字をローマ字(ヘボン式)に変換します。
import "github.com/kotaroooo0/gojaconv/jaconv"
hebon := jaconv.ToHebon("おはよう")
fmt.Println(hebon)
// Output: ohayo
hebon = jaconv.ToHebon("こんにちは")
fmt.Println(hebon)
// Output: konnichiha
入力に対して、ローマ字に直して処理したい場合に処理するものが見当たらなかったので自作しました。例えば、僕はTwitterBotを作成しており、リプライのテキストをローマ字文字列集合との類似度を計算したい場面に遭遇しました。その場合、リプライのテキストが元々ローマ字であれば問題ないですが、そうとは限らないのでこのライブラリを使いローマ字に変換することで解決しました。
ちなみにテストカバレッジ100%です。
ok github.com/kotaroooo0/gojaconv/jaconv 3.344s coverage: 100.0% of statements
実装
以下のような愚直なマッピングがベースにあります。
type CharHebon struct {
Char string
Hebon string
}
func charHebonByIndex(kana string, index int) CharHebon {
hebonMap := map[string]string{
"あ": "a", "い": "i", "う": "u", "え": "e", "お": "o",
"か": "ka", "き": "ki", "く": "ku", "け": "ke", "こ": "ko",
"さ": "sa", "し": "shi", "す": "su", "せ": "se", "そ": "so",
"た": "ta", "ち": "chi", "つ": "tsu", "て": "te", "と": "to",
"な": "na", "に": "ni", "ぬ": "nu", "ね": "ne", "の": "no",
"は": "ha", "ひ": "hi", "ふ": "fu", "へ": "he", "ほ": "ho",
"ま": "ma", "み": "mi", "む": "mu", "め": "me", "も": "mo",
"や": "ya", "ゆ": "yu", "よ": "yo",
"ら": "ra", "り": "ri", "る": "ru", "れ": "re", "ろ": "ro",
"わ": "wa", "ゐ": "i", "ゑ": "e", "を": "o",
"ぁ": "a", "ぃ": "i", "ぅ": "u", "ぇ": "e", "ぉ": "o",
"が": "ga", "ぎ": "gi", "ぐ": "gu", "げ": "ge", "ご": "go",
"ざ": "za", "じ": "ji", "ず": "zu", "ぜ": "ze", "ぞ": "zo",
"だ": "da", "ぢ": "ji", "づ": "zu", "で": "de", "ど": "do",
"ば": "ba", "び": "bi", "ぶ": "bu", "べ": "be", "ぼ": "bo",
"ぱ": "pa", "ぴ": "pi", "ぷ": "pu", "ぺ": "pe", "ぽ": "po",
"きゃ": "kya", "きゅ": "kyu", "きょ": "kyo",
"しゃ": "sha", "しゅ": "shu", "しょ": "sho",
"ちゃ": "cha", "ちゅ": "chu", "ちょ": "cho", "ちぇ": "che",
"にゃ": "nya", "にゅ": "nyu", "にょ": "nyo",
"ひゃ": "hya", "ひゅ": "hyu", "ひょ": "hyo",
"みゃ": "mya", "みゅ": "myu", "みょ": "myo",
"りゃ": "rya", "りゅ": "ryu", "りょ": "ryo",
"ぎゃ": "gya", "ぎゅ": "gyu", "ぎょ": "gyo",
"じゃ": "ja", "じゅ": "ju", "じょ": "jo",
"びゃ": "bya", "びゅ": "byu", "びょ": "byo",
"ぴゃ": "pya", "ぴゅ": "pyu", "ぴょ": "pyo",
}
var hebon string
var char string
utfstr := utf8string.NewString(kana)
// 2文字ヒットするとき
if index+1 < utf8.RuneCountInString(kana) {
char = utfstr.Slice(index, index+2)
hebon = hebonMap[char]
}
// 2文字はヒットしないが1文字はヒットするとき
if hebon == "" && index < utfstr.RuneCount() {
char = utfstr.Slice(index, index+1)
hebon = hebonMap[char]
}
return CharHebon{Char: char, Hebon: hebon}
}
次に以下のルールにしたがって、ローマ字を構築していきます。
- 撥音:B、M、Pの前の「ん」は、NではなくMで表記します。
- 促音:子音を重ねて表記します。
- 長音:OやUは記入しません。
func ToHebon(kana string) string {
isOmitted := map[string]bool{
"aa": true, "ee": true, "ii": false, // i は連続しても省略しない
"oo": true, "ou": true, "uu": true,
}
var hebon string
var lastHebon string
i := 0
for {
ch := charHebonByIndex(kana, i)
if ch.Char == "っ" {
// "っち"
nextCh := charHebonByIndex(kana, i+1)
if nextCh.Hebon != "" {
ch.Hebon = "t"
}
} else if ch.Char == "ん" {
// B,M,P の前の "ん" は "M" とする。
nextCh := charHebonByIndex(kana, i+1)
if nextCh.Hebon != "" && strings.Index("bmp", nextCh.Hebon[0:1]) != -1 {
ch.Hebon = "m"
} else {
ch.Hebon = "n"
}
} else if ch.Char == "ー" {
// 長音は無視
ch.Hebon = ""
}
if ch.Hebon != "" {
// 変換できる文字の場合
if lastHebon != "" {
// 連続する母音の除去
joinedHebon := lastHebon + ch.Hebon
if len(joinedHebon) > 2 {
joinedHebon = joinedHebon[len(joinedHebon)-2:]
}
if isOmitted[joinedHebon] {
ch.Hebon = ""
}
}
hebon += ch.Hebon
} else {
if ch.Char != "ー" {
// 変換できない文字の場合
hebon += ch.Char
}
}
lastHebon = ch.Hebon
i += utf8.RuneCountInString(ch.Char)
if i >= utf8.RuneCountInString(kana) {
break
}
}
return hebon
}