pub type Cost = usize;
fn sequence_alignment(s1: &str, s2: &str, gap_penalty: Cost, mismatch_penalty: Cost) -> Cost {
let mut lut = Vec::<Vec<Cost>>::with_capacity(s1.len() + 1);
for i in 0..=s1.len() {
lut.push(Vec::<Cost>::with_capacity(s2.len() + 1));
for j in 0..=s2.len() {
match i {
0 => lut[i].push(j * gap_penalty),
_ => match j {
0 => lut[i].push(i * gap_penalty),
_ => lut[i].push(0),
},
}
}
}
let min3 = |x, y, z| -> Cost {
let mut min = x;
if y < min {
min = y;
}
if z < min {
min = z;
}
min
};
let mut s1_it = s1.chars().enumerate();
while let Some((i, c1)) = s1_it.next() {
let mut s2_it = s2.chars().enumerate();
while let Some((j, c2)) = s2_it.next() {
lut[i + 1][j + 1] = min3(
mismatch_penalty * ((c1 != c2) as Cost) + lut[i][j],
gap_penalty + lut[i][j + 1],
gap_penalty + lut[i + 1][j],
);
}
}
lut[s1.len()][s2.len()]
}
pub fn sel_min_edit_str<'a, T: AsRef<str>>(
s: &str,
bank: &'a [T],
threshold: Cost,
) -> Option<&'a str> {
let (w, c) = bank
.iter()
.map(|f| (f, sequence_alignment(s, f.as_ref(), 1, 1)))
.min_by(|x, y| x.1.cmp(&y.1))?;
if c < threshold {
Some(w.as_ref())
} else {
None
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn it_works() {
assert_eq!(sequence_alignment("identity", "similarity", 2, 1), 8);
assert_eq!(sequence_alignment("palate", "palette", 2, 1), 3);
assert_eq!(sequence_alignment("ctaccg", "tacatg", 2, 1), 5);
assert_eq!(sequence_alignment("stop", "tops", 2, 1), 4);
assert_eq!(sequence_alignment("ocurrance", "occurrence", 2, 1), 3);
assert_eq!(sequence_alignment("go gators", "go gators", 2, 1), 0);
assert_eq!(sequence_alignment("", "alpha", 2, 1), 10);
assert_eq!(sequence_alignment("", "", 2, 1), 0);
assert_eq!(sequence_alignment("--verbsoe", "--verbose", 1, 1), 2);
assert_eq!(sequence_alignment("--verbsoe", "--version", 1, 1), 3);
assert_eq!(sequence_alignment("ALPHA", "alpha", 2, 1), 5);
}
#[test]
fn get_closest_word() {
let bank: Vec<&str> = vec![];
assert_eq!(sel_min_edit_str("word", &bank, 3), None);
let bank: Vec<&str> = vec!["run", "check", "build", "plan", "config", "play", "digit"];
assert_eq!(sel_min_edit_str("buif", &bank, 3), Some("build"));
assert_eq!(sel_min_edit_str("word", &bank, 3), None);
assert_eq!(sel_min_edit_str("plug", &bank, 3), Some("plan"));
assert_eq!(sel_min_edit_str("cck", &bank, 3), Some("check"));
assert_eq!(sel_min_edit_str("digt", &bank, 3), Some("digit"));
}
}