Rustで言語処理100本ノックしています。
第4章: 形態素解析
開発環境上にインストールしたmecabを用いています。辞書も開発環境のパッケージマネージャに用意されているものをそのまま用いています。
後編はこちら
30. 形態素解析結果の読み込み
形態素解析結果(neko.txt.mecab)を読み込むプログラムを実装せよ.ただし,各形態素は表層形(surface),基本形(base),品詞(pos),品詞細分類1(pos1)をキーとするマッピング型に格納し,1文を形態素(マッピング型)のリストとして表現せよ.第4章の残りの問題では,ここで作ったプログラムを活用せよ.
mecabの出力結果は表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音
と並んでいます。なのでここから各要素を取り出してHashMap
に格納し、文ごとにそれをVec
に格納すればよいです。
pub fn read_mecab(path: &Path) -> Result<Vec<Vec<HashMap<String, String>>>> {
let file = File::open(path)?;
let br = BufReader::new(file);
let mut lines = br.lines();
let mut results = Vec::new();
let mut sentence = Vec::new();
while let Some(Ok(line)) = lines.next() {
if line == "EOS" {
results.push(sentence);
sentence = Vec::new();
} else {
let mut elements = HashMap::new();
let line = line.replace("\t", ",");
let tmp: Vec<_> = line.split_terminator(',').collect();
elements.insert("surface".to_string(), tmp[0].to_string());
elements.insert("base".to_string(), tmp[7].to_string());
elements.insert("pos".to_string(), tmp[1].to_string());
elements.insert("pos1".to_string(), tmp[2].to_string());
sentence.push(elements);
}
}
Ok(results)
}
31. 動詞、32. 動詞の原形、33. サ変名詞
動詞の表層形をすべて抽出せよ.
動詞の原形をすべて抽出せよ.
サ変接続の名詞をすべて抽出せよ.
この3つはとても単純かつよく似ているので31だけ示します。
pub fn get_verb_surfaces(data: &Vec<Vec<HashMap<String, String>>>) -> Vec<String> {
data.iter()
.flat_map(|sentence| sentence.into_iter()
.filter(|elements| elements["pos"] == "動詞")
.map(|elements| elements["surface"].clone()))
.collect()
}
34. 「AのB」
2つの名詞が「の」で連結されている名詞句を抽出せよ.
pub fn a_no_b(data: &Vec<Vec<HashMap<String, String>>>) -> Vec<String> {
data.iter().filter(|sentence| sentence.len() > 2).flat_map(|sentence| {
let mut results = Vec::new();
for i in 0..(sentence.len() - 2) {
if sentence[i + 1]["surface"] == "の"
&& sentence[i]["pos"] == "名詞"
&& sentence[i + 2]["pos"] == "名詞" {
results.push(sentence[i]["surface"].clone()
+ "の" + &sentence[i + 2]["surface"]);
}
}
results
}).collect()
}
前章が難しかったせいかここまではとても簡単に感じます。
35. 名詞の連接
名詞の連接(連続して出現する名詞)を最長一致で抽出せよ.
あまり綺麗に書けなかったのに加えて、これはインストールしたmecabの辞書の問題でしょうが、これでいいのか?という答えが出てしまったのですが、とりあえず条件には適合していそうです。
pub fn longest_noun(data: &Vec<Vec<HashMap<String, String>>>) -> String {
let mut longest = String::new();
let mut max_length = 0;
let mut tmp = String::new();
let mut current_length = 0;
data.into_iter().for_each(|sentence| {
let mut buffer = String::new();
if !tmp.is_empty() {
buffer.push_str(&tmp);
tmp = String::new();
}
sentence.into_iter().for_each(|elements| {
if elements["pos"] == "名詞" {
buffer.push_str(&elements["surface"]);
current_length += 1;
} else {
if current_length > max_length {
max_length = current_length;
longest = buffer.clone();
}
current_length = 0;
buffer = String::new();
}
});
if !buffer.is_empty() {
tmp = buffer;
}
});
longest
}