use std::cmp::Ordering;
use phf;
use regex::Regex;
use {SplitCaptures};
lazy_static! {
static ref RE_HAN: Regex = Regex::new(r"([\u{4E00}-\u{9FD5}]+)").unwrap();
static ref RE_SKIP: Regex = Regex::new(r"([a-zA-Z0-9]+(?:.\d+)?%?)").unwrap();
}
pub type StatusSet = [f64; 4];
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)]
pub enum Status {
B = 0,
E = 1,
M = 2,
S = 3,
}
static PREV_STATUS: [[Status; 2]; 4] = [
[Status::E, Status::S], [Status::B, Status::M], [Status::M, Status::B], [Status::S, Status::E], ];
include!(concat!(env!("OUT_DIR"), "/hmm_prob.rs"));
const MIN_FLOAT: f64 = -3.14e100;
fn viterbi(sentence: &str, char_indices: &[usize]) -> Vec<Status> {
assert!(char_indices.len() > 1);
let states = [Status::B, Status::M, Status::E, Status::S];
#[allow(non_snake_case)]
let mut V = vec![vec![0.0; states.len()]; char_indices.len()];
let mut path = vec![vec![Status::B; char_indices.len()]; states.len()];
for y in &states {
let first_word = &sentence[char_indices[0]..char_indices[1]];
let prob = INITIAL_PROBS[*y as usize] + EMIT_PROBS[*y as usize].get(first_word).cloned().unwrap_or(MIN_FLOAT);
V[0][*y as usize] = prob;
path[*y as usize][0] = *y;
}
for t in 1..char_indices.len() {
let mut new_path = vec![vec![Status::B; char_indices.len()]; states.len()];
for y in &states {
let byte_start = char_indices[t];
let byte_end = if t + 1 < char_indices.len() {
char_indices[t + 1]
} else {
sentence.len()
};
let word = &sentence[byte_start..byte_end];
let em_prob = EMIT_PROBS[*y as usize].get(word).cloned().unwrap_or(MIN_FLOAT);
let (prob, state) = PREV_STATUS[*y as usize]
.iter()
.map(|y0| {
(V[t - 1][*y0 as usize] + TRANS_PROBS[*y0 as usize].get(*y as usize).cloned().unwrap_or(MIN_FLOAT) + em_prob, *y0)
})
.max_by(|x, y| x.partial_cmp(y).unwrap_or(Ordering::Equal))
.unwrap();
V[t][*y as usize] = prob;
let mut prev_path = path[state as usize].clone();
prev_path[t] = *y;
new_path[*y as usize] = prev_path;
}
path = new_path;
}
let (_prob, state) = [Status::E, Status::S]
.iter().map(|y| {
(V[char_indices.len() - 1][*y as usize], y)
})
.max_by(|x, y| x.partial_cmp(y).unwrap_or(Ordering::Equal))
.unwrap();
let best_path: Vec<Status> = path[*state as usize].iter().map(|x| *x).collect();
best_path
}
fn cut_internal<'a>(sentence: &'a str, char_indices: Vec<usize>) -> Vec<&'a str> {
let path = viterbi(sentence, &char_indices);
let mut begin = 0;
let mut next_i = 0;
let mut words = Vec::with_capacity(char_indices.len() / 2);
for i in 0..char_indices.len() {
let state = path[i];
match state {
Status::B => begin = i,
Status::E => {
let byte_start = char_indices[begin];
let byte_end = if i + 1 < char_indices.len() {
char_indices[i + 1]
} else {
sentence.len()
};
words.push(&sentence[byte_start..byte_end]);
next_i = i + 1;
},
Status::S => {
let byte_start = char_indices[i];
let byte_end = if i + 1 < char_indices.len() {
char_indices[i + 1]
} else {
sentence.len()
};
words.push(&sentence[byte_start..byte_end]);
next_i = i + 1;
},
Status::M => { },
}
}
if next_i < char_indices.len() {
let byte_start = char_indices[next_i];
words.push(&sentence[byte_start..]);
}
words
}
pub fn cut<'a>(sentence: &'a str) -> Vec<&'a str> {
let mut words = Vec::new();
let splitter = SplitCaptures::new(&RE_HAN, sentence);
for state in splitter {
let block = state.as_str();
if block.is_empty() {
continue;
}
if RE_HAN.is_match(block) {
if block.chars().count() > 1 {
let char_indices: Vec<usize> = block.char_indices().map(|x| x.0).collect();
words.extend(cut_internal(block, char_indices));
} else {
words.push(block);
}
} else {
let skip_splitter = SplitCaptures::new(&RE_SKIP, block);
for skip_state in skip_splitter {
let x = skip_state.as_str();
if x.is_empty() {
continue;
}
words.push(x);
}
}
}
words
}
#[cfg(test)]
mod tests {
use super::{viterbi, cut};
#[test]
fn test_viterbi() {
use super::Status::*;
let sentence = "小明硕士毕业于中国科学院计算所";
let char_indices: Vec<usize> = sentence.char_indices().map(|x| x.0).collect();
let path = viterbi(sentence, &char_indices);
assert_eq!(path, vec![B, E, B, E, B, M, E, B, E, B, M, E, B, E, S]);
}
#[test]
fn test_hmm_cut() {
let sentence = "小明硕士毕业于中国科学院计算所";
let words = cut(sentence);
assert_eq!(words, vec!["小明", "硕士", "毕业于", "中国", "科学院", "计算", "所"]);
}
}