#[must_use]
pub fn typo_distance(a: &str, b: &str) -> usize {
let a_chars: Vec<char> = a.chars().collect();
let b_chars: Vec<char> = b.chars().collect();
let m = a_chars.len();
let n = b_chars.len();
let mut dp = vec![vec![0usize; n + 1]; m + 1];
for (i, row) in dp.iter_mut().enumerate() {
row[0] = i;
}
for (j, cell) in dp[0].iter_mut().enumerate() {
*cell = j;
}
for i in 1..=m {
for j in 1..=n {
let substitution = usize::from(a_chars[i - 1] != b_chars[j - 1]);
let mut best = (dp[i - 1][j] + 1)
.min(dp[i][j - 1] + 1)
.min(dp[i - 1][j - 1] + substitution);
if i > 1
&& j > 1
&& a_chars[i - 1] == b_chars[j - 2]
&& a_chars[i - 2] == b_chars[j - 1]
{
best = best.min(dp[i - 2][j - 2] + 1);
}
dp[i][j] = best;
}
}
dp[m][n]
}
#[must_use]
pub fn is_close_token_typo(actual: &str, expected: &str) -> bool {
let actual = actual.to_lowercase();
let expected = expected.to_lowercase();
let actual_len = actual.chars().count();
let expected_len = expected.chars().count();
actual_len.min(expected_len) >= 4 && typo_distance(&actual, &expected) == 1
}