Compare commits

..

No commits in common. "2cbe8462ae53448490e366a6e44662f56d491318" and "4fb005ce09d60066c9d46b8e15b01ffa8f1833a8" have entirely different histories.

6 changed files with 33 additions and 406 deletions

View file

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

View file

@ -1,5 +1,4 @@
mod phfbin_gen;
mod testconv;
use std::{borrow::Cow, collections::HashMap, path::Path};
@ -67,13 +66,12 @@ 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(_) => {
/*
// Replace reading if the new one is shorter
std::collections::hash_map::Entry::Occupied(mut e) => {
// Replace reading if the new one is longer
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());
@ -200,63 +198,9 @@ 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 {

View file

@ -1,196 +0,0 @@
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,7 +12,6 @@ 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' => &['あ', 'ぁ', 'っ', 'わ', 'ゎ'],
@ -40,20 +39,6 @@ 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);
@ -61,131 +46,12 @@ pub fn convert(text: &str) -> KakasiResult {
let text = text.nfkc().collect::<String>();
let text = convert_syn(&text);
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();
let hiragana = convert_kanji(&text, "", &dict).0;
let romaji = wana_kana::to_romaji::to_romaji(&hiragana);
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
@ -195,7 +61,7 @@ fn convert_kana(text: &str) -> String {
/// The input needs to be NFKC-normalized and synonymous kanji need to be
/// replaced using [`convert_syn`].
///
/// * `btext` - Buffer string (leading kana)
/// * `btext` -
///
/// # Return
///
@ -203,7 +69,6 @@ fn convert_kana(text: &str) -> String {
/// * `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();
@ -222,7 +87,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
i_c += 1;
n_c += 1;
hira.push(*next_c);
Some(hira)
} else {
@ -244,14 +109,11 @@ fn convert_kanji(text: &str, btext: &str, dict: &PhfMap) -> (String, usize) {
})
});
i_c += 1;
if let Some(tl) = this_tl {
translation = Some(tl);
n_c = i_c;
}
if i_c >= MAX_KANJI_LEN {
break;
match this_tl {
Some(this_tl) => translation = Some(this_tl),
None => break,
}
n_c += 1;
}
translation
@ -303,9 +165,6 @@ 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!("{}\n{}\n\n", res.hiragana, res.romaji);
println!("{} - {}", res.hiragana, res.romaji);
}
}