use crate::{combined_score_raw, normalize};
use std::collections::BTreeSet;
pub fn token_sort_ratio(a: &str, b: &str) -> f64 {
let a_sorted = sort_tokens(&normalize(a));
let b_sorted = sort_tokens(&normalize(b));
combined_score_raw(&a_sorted, &b_sorted)
}
pub fn token_set_ratio(a: &str, b: &str) -> f64 {
let a_set = set_tokens(&normalize(a));
let b_set = set_tokens(&normalize(b));
combined_score_raw(&a_set, &b_set)
}
fn sort_tokens(s: &str) -> String {
let mut tokens: Vec<&str> = s.split_whitespace().collect();
tokens.sort();
tokens.join(" ")
}
fn set_tokens(s: &str) -> String {
let tokens: BTreeSet<&str> = s.split_whitespace().collect();
tokens.into_iter().collect::<Vec<&str>>().join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
fn approx(a: f64, b: f64) -> bool {
(a - b).abs() < 0.001
}
#[test]
fn test_sort_identical() {
assert!(approx(token_sort_ratio("hello world", "hello world"), 1.0));
}
#[test]
fn test_sort_reordered() {
assert!(approx(token_sort_ratio("hello world", "world hello"), 1.0));
}
#[test]
fn test_sort_product_example() {
let score = token_sort_ratio("Oat Drink Oatly 1L", "Oatly Oat Drink 1L");
assert!(approx(score, 1.0));
}
#[test]
fn test_sort_case_insensitive() {
assert!(approx(token_sort_ratio("HELLO World", "world hello"), 1.0));
}
#[test]
fn test_sort_partial_overlap() {
let score = token_sort_ratio("apple banana", "apple cherry");
assert!(score > 0.0 && score < 1.0);
}
#[test]
fn test_sort_both_empty() {
assert!(approx(token_sort_ratio("", ""), 1.0));
}
#[test]
fn test_set_deduplicates() {
assert!(approx(
token_set_ratio("apple apple banana", "banana apple"),
1.0,
));
}
#[test]
fn test_set_identical() {
assert!(approx(token_set_ratio("hello world", "hello world"), 1.0));
}
#[test]
fn test_set_reordered_and_repeated() {
let score = token_set_ratio("oat oat drink oatly", "oatly drink oat");
assert!(approx(score, 1.0));
}
#[test]
fn test_set_case_insensitive() {
assert!(approx(token_set_ratio("Apple BANANA", "banana apple"), 1.0));
}
#[test]
fn test_set_partial_overlap() {
let score = token_set_ratio("apple banana", "apple cherry");
assert!(score > 0.0 && score < 1.0);
}
#[test]
fn test_set_both_empty() {
assert!(approx(token_set_ratio("", ""), 1.0));
}
}