Compare commits

...

2 commits

Author SHA1 Message Date
2cbe8462ae remove redundant dict entries 2022-11-21 00:34:43 +01:00
9ec50677d9 coversion working, but no spaces 2022-11-20 21:42:06 +01:00
6 changed files with 406 additions and 33 deletions

View file

@ -8317,7 +8317,6 @@
おうようじょう 応用上
おうようじょうほう 応用情報
おうようじょうほうがく 応用情報学
おうじょうけん 応用情報学研究センター
おうようすいしんか 応用推進課
おうようすうがく 応用数学
おうようすうがっか 応用数学科
@ -14908,7 +14907,6 @@
かんいしんぱん 簡易新版
かんいてき 簡易的
かんいほけん 簡易保険
かんいほけんほーる 簡易保険ホール
かんいほうしき 簡易方式
かんいむせん 簡易無線
かんけつ 簡潔
@ -42588,7 +42586,6 @@
しょせきるい 書籍類
しょせん 書泉
しょせんぐらんで 書泉グランデ
しょせんぶっくまーと 書泉ブックマート
しょたい 書体
しょだな 書棚
かきおき 書置
@ -46961,7 +46958,6 @@
しんにっぽん 新日本
しんにほんしょうけん 新日本証券
しんにほんせいてつ 新日本製鉄
しんにってつ 新日本製鉄株式会社
しんにほんせいてつ 新日本製鐵
しんにっぽんせいてつ 新日本製鐵
しんにってつ 新日鐵
@ -54141,7 +54137,6 @@
せんこうちゅう 選考中
せんこうび 選考日
せんこう 選鉱
せんけん 選鉱精錬研究所
せんじゃ 選者
せんしゅ 選手
せんしゅいちらん 選手一覧
@ -62107,7 +62102,6 @@
ちゅうがくせいばん 中学生版
ちゅうがくにゅうしもんだい 中学入試問題
ちゅうかっこ 中括弧
なかま 中間
ちゅうかんれべるがくしゅう 中間レベル学習
ちゅうかんえき 中間駅
ちゅうかんえきしはつ 中間駅始発
@ -66471,7 +66465,6 @@
でんしききぶ 電子機器部
でんしぎじゅつ 電子技術
でんしぎじゅつしゃ 電子技術者
でんそうけん 電子技術総合研究所
でんしきょう 電子協
でんしけいじばん 電子掲示板
でんしけい 電子系
@ -66486,7 +66479,6 @@
でんしこうがくきょうしつ 電子工学教室
でんしこうがくせんこう 電子工学専攻
でんしこうぎょう 電子工業
でんしきょう 電子工業振興協会
でんしこうさく 電子工作
でんしざいりょう 電子材料
でんししき 電子式
@ -67641,7 +67633,6 @@
とうきょうがす 東京ガス
とうきょうすたいる 東京スタイル
とうきょうてあとる 東京テアトル
とうきょうべいえぬけーほーる 東京ベイNKホール
とうきょういがい 東京以外
とうきょういち 東京一
とうきょうえき 東京駅
@ -68467,7 +68458,6 @@
とうけいしょり 統計処理
とうけいじょうほう 統計情報
とうけいすうがく 統計数学
とうすうけん 統計数理研究所
とうけいち 統計値
とうけいてき 統計的
とうけいてきぱたあん 統計的パターン
@ -71469,12 +71459,10 @@
にっぽんとむそん 日本トムソン
にっぽんはむ 日本ハム
にほんはむ 日本ハム
にっぽんひゅーむかん 日本ヒューム管
にっぽんびくたー 日本ビクター
にっぽんぺいんと 日本ペイント
にっぽんゆにばっく 日本ユニバック
にっぽんれーす 日本レース
にほんろぼっとがっかい 日本ロボット学会
にほんいがい 日本以外
にほんいじょう 日本以上
にほんいち 日本一
@ -72032,8 +72020,6 @@
にゅうしゅつりょく 入出力
にゅうしゅつりょくh 入出力
にゅうしゅつりょくせっと 入出力セット
にゅうしゅつりょくぱ 入出力パターン
にゅうしゅつりょくぱたーん 入出力パターン
にゅうしゅつりょくかんけい 入出力関係
にゅうしゅつりょくけい 入出力系
にゅうしゅつりょくそうち 入出力装置
@ -79676,7 +79662,6 @@
ぶんいちそうごうしゅっぱん 文一総合出版
ぶんえんどう 文苑堂
ぶんか 文化
ぶんかしゃったー 文化シャッター
ぶんかかい 文化会
ぶんかかいかん 文化会館
ぶんかかいかん 文化会舘
@ -80078,7 +80063,6 @@
へいおん 平温
へいおん 平穏
ひらかな 平仮名
ひらかなかくていにゅうりょく 平仮名確定入力
へいか 平価
へいけ 平家
へいけものがたり 平家物語
@ -110778,7 +110762,6 @@
せんせいよう 先生用
せんせんげつ 先先月
せんたんいりょう 先端医療
せんたんかがくぎじゅつけんきゅうせんたー 先端科学技術研究センター
せんたんぎじゅつけんきゅう 先端技術研究
せんたんきょうふしょう 先端恐怖症
せんたんけん 先端研
@ -114513,7 +114496,6 @@
でんしききるい 電子機器類
でんしきどう 電子軌道
でんしぎじゅつかんけい 電子技術関係
でんしぎじゅつそうごうけんきゅうしょ 電子技術総合研究所
でんしけいじばん 電子掲示版
でんしけいさんきしつ 電子計算機室
でんしこうさくよう 電子工作用
@ -116022,7 +116004,6 @@
にほんちんぼつ 日本沈没
にほんていえん 日本庭園
にほんてつどう 日本鉄道
にほんにんちかがくかい 日本認知科学会
にほんねこ 日本猫
にほんばんじまく 日本版字幕
にほんぶっきょう 日本仏教
@ -116115,7 +116096,6 @@
にゅうよくざい 入浴剤
にゅうよくはっぽうき 入浴発泡器
にゅうらい 入来
にゅうりょくぱらめーた 入力パラメータ
にゅうりょくかんじ 入力漢字
にゅうりょくかんきょう 入力環境
にゅうりょくじたい 入力自体

View file

@ -1,4 +1,5 @@
mod phfbin_gen;
mod testconv;
use std::{borrow::Cow, collections::HashMap, path::Path};
@ -66,12 +67,13 @@ fn parse_dict_ln(records: &mut Records, line: &str, ln: usize) {
.or_else(|| context.map(str::to_owned))
.unwrap_or_default(),
) {
std::collections::hash_map::Entry::Occupied(mut e) => {
// Replace reading if the new one is longer
std::collections::hash_map::Entry::Occupied(_) => {
/*
// Replace reading if the new one is shorter
let val = e.get_mut();
if val.len() < reading.len() {
if val.len() > reading.len() {
*val = reading.to_owned();
}
}*/
}
std::collections::hash_map::Entry::Vacant(e) => {
e.insert(reading.to_owned());
@ -198,9 +200,63 @@ impl Encodable for Readings {
}
}
fn find_redundant_compounds(dict: &Records) -> Records {
let mut wdict = dict.clone();
for (kanji, readings) in dict {
if kanji.chars().count() <= 3 {
continue;
}
if readings.len() != 1 {
continue;
}
if let Some(reading) = readings.get("") {
// Try to convert the entry without it being present
let entry = wdict.remove_entry(kanji).unwrap();
let res = testconv::convert(kanji, &wdict);
if &res == reading || to_romaji_nodc(&res) == to_romaji_nodc(reading) {
println!("Redundant: {} - {}", kanji, reading);
} else {
// Put the entry back if it is necessary
wdict.insert(entry.0, entry.1);
}
}
}
wdict
}
/// Romanize and remove double consonants
fn to_romaji_nodc(text: &str) -> String {
let rom = wana_kana::to_romaji::to_romaji(text);
let mut buf = String::new();
let mut citer = rom.chars().peekable();
while let Some(c) = citer.next() {
if matches!(c, 'a' | 'e' | 'i' | 'o' | 'u') {
match citer.peek() {
Some(nc) => {
if &c != nc {
buf.push(c);
}
}
None => buf.push(c),
}
} else {
buf.push(c);
}
}
buf
}
fn generate_kanji_dict() -> Vec<u8> {
let mut records = Records::default();
parse_dict(&mut records, Path::new("dict/kakasidict.utf8"));
records = find_redundant_compounds(&records);
println!("kanji_dict: {} entries", records.len());
let mut phfmap = phfbin_gen::Map::<KanjiString, Readings>::default();
for (kanji, readings) in records {

196
codegen/src/testconv.rs Normal file
View file

@ -0,0 +1,196 @@
use crate::{Records, CLETTERS};
const ENDMARK: [char; 11] = [
')', ']', '!', '.', ',', '\u{3001}', '\u{3002}', '\u{ff1f}', '\u{ff10}', '\u{ff1e}', '\u{ff1c}',
];
const DASH_SYMBOLS: [char; 4] = ['\u{30FC}', '\u{2015}', '\u{2212}', '\u{FF70}'];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CharType {
Kanji,
Katakana,
Hiragana,
Symbol,
Alpha,
}
pub fn convert(text: &str, dict: &Records) -> String {
// TODO: char conversion should be done with iterators
let mut char_indices = text.char_indices();
let mut kana_text = String::new();
let mut hiragana = String::new();
let mut prev_type = CharType::Kanji;
// output_flag
// means (output buffer?, output text[i]?, copy to buffer and increment i?)
// possible (False, True, True), (True, False, False), (True, True, True)
// (False, False, True)
while let Some((i, c)) = char_indices.next() {
let output_flag = if ENDMARK.contains(&c) {
(CharType::Symbol, true, true, true)
} else if DASH_SYMBOLS.contains(&c) {
(prev_type, false, false, true)
} else if is_sym(c) {
if prev_type != CharType::Symbol {
(CharType::Symbol, true, false, true)
} else {
(CharType::Symbol, false, true, true)
}
} else if wana_kana::utils::is_char_katakana(c) {
(
CharType::Katakana,
prev_type != CharType::Katakana,
false,
true,
)
} else if wana_kana::utils::is_char_hiragana(c) {
(
CharType::Hiragana,
prev_type != CharType::Hiragana,
false,
true,
)
} else if c.is_ascii() {
(CharType::Alpha, prev_type != CharType::Alpha, false, true)
} else if wana_kana::utils::is_char_kanji(c) {
if !kana_text.is_empty() {
hiragana.push_str(&convert_kana(&kana_text));
}
let (t, n) = convert_kanji(&text[i..], &kana_text, &dict);
if n > 0 {
kana_text = t;
for _ in 1..n {
char_indices.next();
}
(CharType::Kanji, false, false, false)
} else {
// Unknown kanji
kana_text.clear();
// TODO: FOR TESTING
hiragana.push_str("🯄");
(CharType::Kanji, true, false, false)
}
} else if matches!(c as u32, 0xf000..=0xfffd | 0x10000..=0x10ffd) {
// PUA: ignore and drop
if !kana_text.is_empty() {
hiragana.push_str(&convert_kana(&kana_text));
}
(prev_type, false, false, false)
} else {
(prev_type, true, true, true)
};
prev_type = output_flag.0;
if output_flag.1 && output_flag.2 {
kana_text.push(c);
hiragana.push_str(&convert_kana(&kana_text));
kana_text.clear()
} else if output_flag.1 && output_flag.3 {
if !kana_text.is_empty() {
hiragana.push_str(&convert_kana(&kana_text));
}
kana_text = c.to_string();
} else if output_flag.3 {
kana_text.push(c);
}
}
// Convert last word
if !kana_text.is_empty() {
hiragana.push_str(&convert_kana(&kana_text));
}
hiragana
}
fn is_sym(c: char) -> bool {
matches!(c as u32,
0x3000..=0x3020 |
0x3030..=0x303F |
0x0391..=0x03A1 |
0x03A3..=0x03A9 |
0x03B1..=0x03C9 |
0x0410..= 0x044F |
0xFF01..=0xFF1A |
0x00A1..=0x00FF |
0xFF20..=0xFF5E |
0x0451 |
0x0401
)
}
fn convert_kana(text: &str) -> String {
wana_kana::to_hiragana::to_hiragana_with_opt(
text,
wana_kana::Options {
use_obsolete_kana: false,
pass_romaji: true,
upcase_katakana: false,
imemode: false,
},
)
}
/// Convert the leading kanji from the input string to hiragana
fn convert_kanji(text: &str, btext: &str, dict: &Records) -> (String, usize) {
let mut translation: Option<String> = None;
let mut i_c = 0;
let mut n_c = 0;
let mut char_indices = text.char_indices().peekable();
while let Some((i, c)) = char_indices.next() {
let kanji = &text[0..i + c.len_utf8()];
let this_tl = dict.get(kanji).and_then(|readings| {
readings
.iter()
.find_map(|(k, reading)| {
if k.is_empty() {
None
} else if let Some(cltr) = CLETTERS.get(&k.chars().next().unwrap_or_default()) {
char_indices.peek().and_then(|(_, next_c)| {
// Shortcut if the next character is not hiragana
if wana_kana::utils::is_char_hiragana(*next_c) {
if cltr.contains(&&next_c.to_string().as_str()) {
// Add the next character to the char count
i_c += 1;
let mut hira = reading.to_owned();
hira.push(*next_c);
return Some(hira);
} else {
None
}
} else {
None
}
})
} else if wana_kana::is_hiragana::is_hiragana(&k) {
if btext.contains(reading) {
Some(reading.to_owned())
} else {
None
}
} else {
panic!("invalid reading key")
}
})
.or_else(|| readings.get("").cloned())
});
i_c += 1;
if let Some(tl) = this_tl {
translation = Some(tl);
n_c = i_c;
}
if i_c >= 12 {
break;
}
}
translation
.map(|tl| (tl.to_owned(), n_c))
.unwrap_or_default()
}

Binary file not shown.

View file

@ -12,6 +12,7 @@ use phfbin::PhfMap;
use types::{KanjiString, Readings};
const KANJI_DICT: &[u8] = include_bytes!("./kanji_dict.bin");
const MAX_KANJI_LEN: usize = 7;
static CLETTERS: phf::Map<u8, &[char]> = phf::phf_map!(
b'a' => &['あ', 'ぁ', 'っ', 'わ', 'ゎ'],
@ -39,6 +40,20 @@ static CLETTERS: phf::Map<u8, &[char]> = phf::phf_map!(
b'v' => &['ゔ'],
);
const ENDMARK: [char; 11] = [
')', ']', '!', '.', ',', '\u{3001}', '\u{3002}', '\u{ff1f}', '\u{ff10}', '\u{ff1e}', '\u{ff1c}',
];
const DASH_SYMBOLS: [char; 4] = ['\u{30FC}', '\u{2015}', '\u{2212}', '\u{FF70}'];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CharType {
Kanji,
Katakana,
Hiragana,
Symbol,
Alpha,
}
pub fn convert(text: &str) -> KakasiResult {
let dict = PhfMap::new(KANJI_DICT);
@ -46,12 +61,131 @@ pub fn convert(text: &str) -> KakasiResult {
let text = text.nfkc().collect::<String>();
let text = convert_syn(&text);
let hiragana = convert_kanji(&text, "", &dict).0;
let romaji = wana_kana::to_romaji::to_romaji(&hiragana);
let mut char_indices = text.char_indices();
let mut kana_text = String::new();
let mut prev_type = CharType::Kanji;
let mut hiragana = String::new();
let mut romaji = String::new();
let conv_kana_txt = |kana_text: &mut String, hiragana: &mut String, romaji: &mut String| {
if !kana_text.is_empty() {
let h = convert_kana(&kana_text);
hiragana.push_str(&h);
romaji.push_str(&wana_kana::to_romaji::to_romaji(&h));
romaji.push(' ');
}
};
// output_flag
// means (output buffer?, output text[i]?, copy to buffer and increment i?)
// possible (False, True, True), (True, False, False), (True, True, True)
// (False, False, True)
while let Some((i, c)) = char_indices.next() {
let output_flag = if ENDMARK.contains(&c) {
(CharType::Symbol, true, true, true)
} else if DASH_SYMBOLS.contains(&c) {
(prev_type, false, false, true)
} else if is_sym(c) {
if prev_type != CharType::Symbol {
(CharType::Symbol, true, false, true)
} else {
(CharType::Symbol, false, true, true)
}
} else if wana_kana::utils::is_char_katakana(c) {
(
CharType::Katakana,
prev_type != CharType::Katakana,
false,
true,
)
} else if wana_kana::utils::is_char_hiragana(c) {
(
CharType::Hiragana,
prev_type != CharType::Hiragana,
false,
true,
)
} else if c.is_ascii() {
(CharType::Alpha, prev_type != CharType::Alpha, false, true)
} else if wana_kana::utils::is_char_kanji(c) {
conv_kana_txt(&mut kana_text, &mut hiragana, &mut romaji);
let (t, n) = convert_kanji(&text[i..], &kana_text, &dict);
if n > 0 {
kana_text = t;
for _ in 1..n {
char_indices.next();
}
(CharType::Kanji, false, false, false)
} else {
// Unknown kanji
kana_text.clear();
// TODO: FOR TESTING
hiragana.push_str("🯄");
romaji.push_str("🯄");
(CharType::Kanji, true, false, false)
}
} else if matches!(c as u32, 0xf000..=0xfffd | 0x10000..=0x10ffd) {
// PUA: ignore and drop
conv_kana_txt(&mut kana_text, &mut hiragana, &mut romaji);
kana_text.clear();
(prev_type, false, false, false)
} else {
(prev_type, true, true, true)
};
prev_type = output_flag.0;
if output_flag.1 && output_flag.2 {
kana_text.push(c);
conv_kana_txt(&mut kana_text, &mut hiragana, &mut romaji);
kana_text.clear()
} else if output_flag.1 && output_flag.3 {
conv_kana_txt(&mut kana_text, &mut hiragana, &mut romaji);
kana_text = c.to_string();
} else if output_flag.3 {
kana_text.push(c);
}
}
// Convert last word
conv_kana_txt(&mut kana_text, &mut hiragana, &mut romaji);
// Remove trailing space
romaji.pop();
KakasiResult { hiragana, romaji }
}
fn is_sym(c: char) -> bool {
matches!(c as u32,
0x3000..=0x3020 |
0x3030..=0x303F |
0x0391..=0x03A1 |
0x03A3..=0x03A9 |
0x03B1..=0x03C9 |
0x0410..= 0x044F |
0xFF01..=0xFF1A |
0x00A1..=0x00FF |
0xFF20..=0xFF5E |
0x0451 |
0x0401
)
}
fn convert_kana(text: &str) -> String {
wana_kana::to_hiragana::to_hiragana_with_opt(
text,
wana_kana::Options {
use_obsolete_kana: false,
pass_romaji: true,
upcase_katakana: false,
imemode: false,
},
)
}
/// Convert the leading kanji from the input string to hiragana
///
/// # Arguments
@ -61,7 +195,7 @@ pub fn convert(text: &str) -> KakasiResult {
/// The input needs to be NFKC-normalized and synonymous kanji need to be
/// replaced using [`convert_syn`].
///
/// * `btext` -
/// * `btext` - Buffer string (leading kana)
///
/// # Return
///
@ -69,6 +203,7 @@ pub fn convert(text: &str) -> KakasiResult {
/// * `1` - Number of converted chars from the input string
fn convert_kanji(text: &str, btext: &str, dict: &PhfMap) -> (String, usize) {
let mut translation = None;
let mut i_c = 0;
let mut n_c = 0;
let mut char_indices = text.char_indices().peekable();
@ -87,7 +222,7 @@ fn convert_kanji(text: &str, btext: &str, dict: &PhfMap) -> (String, usize) {
CLETTERS.get(&ch).and_then(|cltr| {
if cltr.contains(next_c) {
// Add the next character to the char count
n_c += 1;
i_c += 1;
hira.push(*next_c);
Some(hira)
} else {
@ -109,11 +244,14 @@ fn convert_kanji(text: &str, btext: &str, dict: &PhfMap) -> (String, usize) {
})
});
match this_tl {
Some(this_tl) => translation = Some(this_tl),
None => break,
i_c += 1;
if let Some(tl) = this_tl {
translation = Some(tl);
n_c = i_c;
}
if i_c >= MAX_KANJI_LEN {
break;
}
n_c += 1;
}
translation
@ -165,6 +303,9 @@ mod tests {
#[rstest]
#[case("会っAbc", "あっ", 2)]
#[case("渋谷", "しぶや", 2)]
// #[case("渋谷公会堂", "しぶやこうかいどう", 5)]
// #[case("家畜衛生試験場", "かちくえいせいしけんじょう", 7)]
fn t_convert_kanji(#[case] text: &str, #[case] expect: &str, #[case] expect_n: usize) {
let dict = PhfMap::new(KANJI_DICT);
let (res, n) = convert_kanji(text, "", &dict);

View file

@ -1,6 +1,6 @@
fn main() {
for line in std::io::stdin().lines() {
let res = kakasi::convert(&line.unwrap());
println!("{} - {}", res.hiragana, res.romaji);
println!("{}\n{}\n\n", res.hiragana, res.romaji);
}
}