pub struct TypoPatterns;
impl TypoPatterns {
pub fn nearby_keys(ch: char) -> Vec<char> {
match ch.to_ascii_lowercase() {
'q' => vec!['w', 'a', 's'],
'w' => vec!['q', 'e', 'a', 's', 'd'],
'e' => vec!['w', 'r', 's', 'd', 'f'],
'r' => vec!['e', 't', 'd', 'f', 'g'],
't' => vec!['r', 'y', 'f', 'g', 'h'],
'y' => vec!['t', 'u', 'g', 'h', 'j'],
'u' => vec!['y', 'i', 'h', 'j', 'k'],
'i' => vec!['u', 'o', 'j', 'k', 'l'],
'o' => vec!['i', 'p', 'k', 'l'],
'p' => vec!['o', 'l'],
'a' => vec!['q', 'w', 's', 'z'],
's' => vec!['a', 'd', 'w', 'e', 'z', 'x'],
'd' => vec!['s', 'f', 'e', 'r', 'x', 'c'],
'f' => vec!['d', 'g', 'r', 't', 'c', 'v'],
'g' => vec!['f', 'h', 't', 'y', 'v', 'b'],
'h' => vec!['g', 'j', 'y', 'u', 'b', 'n'],
'j' => vec!['h', 'k', 'u', 'i', 'n', 'm'],
'k' => vec!['j', 'l', 'i', 'o', 'm'],
'l' => vec!['k', 'o', 'p', 'm'],
'z' => vec!['a', 's', 'x'],
'x' => vec!['z', 'c', 's', 'd'],
'c' => vec!['x', 'v', 'd', 'f'],
'v' => vec!['c', 'b', 'f', 'g'],
'b' => vec!['v', 'n', 'g', 'h'],
'n' => vec!['b', 'm', 'h', 'j'],
'm' => vec!['n', 'j', 'k', 'l'],
_ => vec![],
}
}
#[allow(clippy::needless_range_loop)]
pub fn keyboard_distance(s1: &str, s2: &str) -> f64 {
let len1 = s1.chars().count();
let len2 = s2.chars().count();
if len1 == 0 {
return len2 as f64;
}
if len2 == 0 {
return len1 as f64;
}
let s1_chars: Vec<char> = s1.chars().collect();
let s2_chars: Vec<char> = s2.chars().collect();
let mut matrix = vec![vec![0.0; len2 + 1]; len1 + 1];
for i in 0..=len1 {
matrix[i][0] = i as f64;
}
for j in 0..=len2 {
matrix[0][j] = j as f64;
}
for i in 1..=len1 {
for j in 1..=len2 {
let ch1 = s1_chars[i - 1];
let ch2 = s2_chars[j - 1];
let substitution_cost = if ch1 == ch2 {
0.0
} else {
let nearby = Self::nearby_keys(ch1);
if nearby.contains(&ch2) {
0.5 } else {
1.0 }
};
matrix[i][j] = (matrix[i - 1][j] + 1.0) .min(matrix[i][j - 1] + 1.0) .min(matrix[i - 1][j - 1] + substitution_cost); }
}
matrix[len1][len2]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_typo_patterns_nearby_keys() {
let nearby_q = TypoPatterns::nearby_keys('q');
assert!(nearby_q.contains(&'w'));
assert!(nearby_q.contains(&'a'));
assert!(!nearby_q.contains(&'z'));
let nearby_m = TypoPatterns::nearby_keys('m');
assert!(nearby_m.contains(&'n'));
assert!(nearby_m.contains(&'j'));
}
#[test]
fn test_keyboard_distance() {
assert!((TypoPatterns::keyboard_distance("search", "search") - 0.0).abs() < 1e-6);
let nearby_dist = TypoPatterns::keyboard_distance("search", "searcg"); let regular_dist = TypoPatterns::keyboard_distance("search", "searcp"); assert!(nearby_dist < regular_dist);
}
}