use std::collections::HashMap;
use rand::Rng;
const BEGIN: &str = "___BEGIN__";
const END: &str = "___END__";
const STATE_SIZE: usize = 2;
type State = Vec<String>;
type Weight = HashMap<String, i32>;
type Model = HashMap<State, Weight>;
#[derive(Debug, Default)]
pub struct Chain {
model: Model,
begin_choices: Vec<String>,
begin_weights: Vec<i32>,
}
impl Chain {
fn accumulate(ns: &[i32]) -> Vec<i32> {
let mut numbers: Vec<i32> = Vec::with_capacity(ns.len());
let mut total = ns[0];
for &n in ns {
numbers.push(total);
total += n; }
numbers
}
fn compile_next(data: &Weight) -> (Vec<String>, Vec<i32>) {
let words: Vec<String> = data.keys().cloned().collect();
let weights: Vec<i32> = data.values().cloned().collect();
let cum: Vec<i32> = Self::accumulate(&weights);
(words, cum)
}
fn bisect_right<T: Ord>(slice: &[T], x: &T) -> usize {
match slice.binary_search(x) {
Ok(idx) => idx + 1,
Err(idx) => idx,
}
}
}
impl Chain {
pub fn new(data: &[Vec<String>]) -> Self {
let mut chain = Self::default();
chain.model = chain.build(data);
chain.compute();
chain
}
fn build(&self, data: &[Vec<String>]) -> Model {
let mut model: Model = HashMap::new();
for run in data {
let mut items: Vec<&str> = vec![BEGIN; STATE_SIZE];
items.extend(run.iter().map(|s| s.as_str()));
items.push(END);
for i in 0..run.len() + 1 {
let state: State = items[i..i + STATE_SIZE]
.iter()
.map(|s| (*s).to_string())
.collect::<Vec<String>>();
let follow: &str = items[i + STATE_SIZE];
model
.entry(state)
.or_insert_with(HashMap::new)
.entry(follow.to_string())
.and_modify(|e| *e += 1)
.or_insert(1);
}
}
model
}
fn begin_state(&self) -> State {
vec![BEGIN.to_string(); STATE_SIZE]
}
fn compute(&mut self) {
let begin_state = self.begin_state();
if let Some(weights) = self.model.get(&begin_state) {
let (choices, cum) = Self::compile_next(weights);
self.begin_choices = choices;
self.begin_weights = cum;
}
}
pub fn next(&self, state: &State) -> String {
let (mut choices, mut cumdist) = (self.begin_choices.clone(), self.begin_weights.clone());
if state != &self.begin_state() {
choices.clear();
cumdist.clear();
let mut weights: Vec<i32> = Vec::new();
for (word, weight) in self.model.get(state).unwrap() {
choices.push(word.clone());
weights.push(*weight);
}
cumdist = Self::accumulate(&weights);
}
let r: f32 = rand::rng().random_range(0.0..1.0) * (*cumdist.last().unwrap() as f32);
let r_i32 = r as i32;
choices[Self::bisect_right(&cumdist, &r_i32)].clone()
}
pub fn generate(&self, init_state: Option<State>) -> Vec<String> {
let mut state = init_state.unwrap_or(self.begin_state());
let mut result: Vec<String> = Vec::new();
loop {
let next_word: String = self.next(&state);
if next_word == END {
break;
}
result.push(next_word.clone());
state.remove(0);
state.push(next_word);
}
result
}
}