pub fn calculate_accuracy(predicted: &str, ground_truth: &str) -> f64 {
if ground_truth.is_empty() {
return 0.0;
}
if predicted.is_empty() {
return 0.0;
}
let distance = levenshtein(predicted, ground_truth);
let max_len = predicted.chars().count().max(ground_truth.chars().count());
if max_len == 0 {
return 100.0;
}
((1.0 - distance as f64 / max_len as f64) * 100.0 * 100.0).round() / 100.0
}
fn levenshtein(s1: &str, s2: &str) -> usize {
let s1_chars: Vec<char> = s1.chars().collect();
let s2_chars: Vec<char> = s2.chars().collect();
let len1 = s1_chars.len();
let len2 = s2_chars.len();
let mut matrix = vec![vec![0; len1 + 1]; len2 + 1];
for (i, cell) in matrix[0].iter_mut().enumerate() {
*cell = i;
}
for (j, row) in matrix.iter_mut().enumerate() {
row[0] = j;
}
for j in 1..=len2 {
for i in 1..=len1 {
let substitution_cost = if s1_chars[i - 1] == s2_chars[j - 1] {
0
} else {
1
};
matrix[j][i] = [
matrix[j][i - 1] + 1, matrix[j - 1][i] + 1, matrix[j - 1][i - 1] + substitution_cost, ]
.into_iter()
.min()
.unwrap();
}
}
matrix[len2][len1]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_levenshtein() {
assert_eq!(levenshtein("kitten", "sitting"), 3);
assert_eq!(levenshtein("", ""), 0);
assert_eq!(levenshtein("a", ""), 1);
assert_eq!(levenshtein("", "a"), 1);
assert_eq!(levenshtein("abc", "abc"), 0);
}
#[test]
fn levenshtein_handles_unequal_lengths() {
assert_eq!(levenshtein("ab", "abcde"), 3);
assert_eq!(levenshtein("abcde", "ab"), 3);
}
#[test]
fn test_accuracy() {
assert_eq!(calculate_accuracy("hello", "hello"), 100.0);
assert_eq!(calculate_accuracy("", "hello"), 0.0);
assert_eq!(calculate_accuracy("hello", ""), 0.0);
let acc = calculate_accuracy("hellp", "hello");
assert!((79.99..=80.01).contains(&acc));
}
#[test]
fn accuracy_uses_the_longer_string_when_lengths_differ() {
assert_eq!(calculate_accuracy("ab", "abcde"), 40.0);
assert_eq!(calculate_accuracy("abcde", "ab"), 40.0);
}
}