use std::iter;
pub const PADDING: char = '.';
pub const VOCABULARY_LEN: usize = 27;
pub fn load_names() -> Vec<&'static str> {
include_str!("names.txt")
.lines()
.map(str::trim)
.filter(|name| !name.is_empty())
.collect()
}
pub fn to_token(character: char) -> usize {
if character == PADDING {
return 0;
}
assert!(
character.is_ascii_lowercase(),
"the corpus holds lowercase ASCII names only, got {character:?}"
);
character as usize - 'a' as usize + 1
}
pub fn from_token(token: usize) -> char {
if token == 0 {
return PADDING;
}
assert!(
token < VOCABULARY_LEN,
"the vocabulary holds {VOCABULARY_LEN} tokens, got {token}"
);
(b'a' + (token - 1) as u8) as char
}
pub fn training_samples<const CONTEXT_LEN: usize>(
names: &[&str],
) -> Vec<([usize; CONTEXT_LEN], usize)> {
let mut samples = Vec::new();
for name in names {
let tokens: Vec<usize> = iter::repeat_n(0, CONTEXT_LEN)
.chain(name.chars().map(to_token))
.chain(iter::once(0))
.collect();
for window in tokens.windows(CONTEXT_LEN + 1) {
let (context, next) = window.split_at(CONTEXT_LEN);
samples.push((
context.try_into().expect("window has context length"),
next[0],
));
}
}
samples
}
pub fn unit(state: &mut u64) -> f64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut mixed = *state;
mixed = (mixed ^ (mixed >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
mixed = (mixed ^ (mixed >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
mixed ^= mixed >> 31;
(mixed >> 11) as f64 / (1u64 << 53) as f64
}
pub fn draw(row: &[f32], state: &mut u64) -> usize {
let mut threshold = unit(state);
for (token, probability) in row.iter().enumerate() {
if threshold < f64::from(*probability) {
return token;
}
threshold -= f64::from(*probability);
}
row.len() - 1
}
pub fn shuffle<T>(samples: &mut [T], state: &mut u64) {
for index in (1..samples.len()).rev() {
let other = (unit(state) * (index + 1) as f64) as usize;
samples.swap(index, other);
}
}