pub fn fstrcmp_bounded(a: &str, b: &str, lower_bound: f64) -> f64 {
let len_1 = a.len();
let len_2 = b.len();
let len_min = len_1.min(len_2);
let len_max = len_1.max(len_2);
let len_sum = len_1 + len_2;
if len_min == 0 {
return if len_max == 0 { 1.0 } else { 0.0 };
}
if lower_bound > 0.0 {
let upper_bound = 2.0 * len_min as f64 / len_sum as f64;
if upper_bound < lower_bound {
return 0.0;
}
if len_sum >= 20 {
let mut occ_diff: [isize; 256] = [0; 256];
for &c in a.as_bytes() {
occ_diff[c as usize] += 1;
}
for &c in b.as_bytes() {
occ_diff[c as usize] -= 1;
}
let occ_diff_sum = occ_diff.map(|i| i.abs() as usize).iter().sum::<usize>();
let upper_bound = 1.0 - occ_diff_sum as f64 / len_sum as f64;
if upper_bound < lower_bound {
return 0.0;
}
}
}
let dist = edit_distance_1d(a, b);
1.0 - (dist as f64 / len_max as f64)
}
pub fn edit_distance_1d(a: &str, b: &str) -> usize {
let (shorter, longer) = if a.len() < b.len() { (a, b) } else { (b, a) };
let s: Vec<char> = shorter.chars().collect();
let l: Vec<char> = longer.chars().collect();
let len_s = s.len();
let len_l = l.len();
let mut prev = (0..=len_s).collect::<Vec<usize>>();
let mut curr = vec![0; len_s + 1];
for (i, lc) in l.iter().enumerate() {
curr[0] = i + 1;
for (j, sc) in s.iter().enumerate() {
let cost = if sc == lc { 0 } else { 1 };
curr[j + 1] = *[
prev[j + 1] + 1, curr[j] + 1, prev[j] + cost, ]
.iter()
.min()
.unwrap();
}
std::mem::swap(&mut prev, &mut curr);
}
prev[len_s]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fstrcmp() {
let a = "kitten";
let b = "sitting";
let r = fstrcmp_bounded(a, b, 0.5);
assert!(r > 0.57 && r < 0.58);
}
}