#![cfg(feature = "parallel")]
use verbora_distance::{
hamming, jaro_winkler,
jaro_winkler::Options as JwOptions,
levenshtein::{Options as LevOptions, damerau_levenshtein, levenshtein},
par_damerau_levenshtein_batch, par_dice_coefficient_batch, par_hamming_batch,
par_jaro_winkler_batch, par_levenshtein_batch,
};
const PATHOLOGICAL: &[(&str, &str)] = &[
("kitten", "sitting"),
("saturday", "sunday"),
("", ""),
("abc", ""),
("", "abc"),
("same", "same"),
("ab", "ba"), ("ca", "abc"), ("abc", "ab"), ("ab", "abc"), ("flaw", "lawn"),
("a", "abcdef"),
("abcdef", "fedcba"),
("MARTHA", "MARHTA"),
("DIXON", "DICKSONX"),
("DWAYNE", "DUANE"),
("a", "b"), ("aaaa", "aa"), ("Hello World", "hello world"),
(" padded ", "padded"),
("night", "nacht"),
("karolin", "kathrin"),
("1011101", "1001001"),
("abc", "abc"),
];
const UNICODE: &[(&str, &str)] = &[
("a😀b", "ab"), ("😀", ""),
("😀", "😀"),
("café", "cafe"),
("Москва", "Москва"),
("a😀b", "abcd"), ("a😀b", "ab"), ];
fn all_pairs() -> Vec<(&'static str, &'static str)> {
PATHOLOGICAL.iter().chain(UNICODE.iter()).copied().collect()
}
fn many_pairs() -> Vec<(&'static str, &'static str)> {
all_pairs().into_iter().cycle().take(4096).collect()
}
fn f64_eq(a: f64, b: f64) -> bool {
a == b || (a.is_nan() && b.is_nan())
}
fn assert_f64_parity(pairs: &[(&str, &str)], seq: impl Fn(&str, &str) -> f64, got: &[f64]) {
assert_eq!(
got.len(),
pairs.len(),
"batch of {} pairs produced {} results",
pairs.len(),
got.len()
);
for (i, (a, b)) in pairs.iter().enumerate() {
let want = seq(a, b);
assert!(
f64_eq(got[i], want),
"pair {i} ({a:?}, {b:?}): parallel={:?} sequential={want:?}",
got[i]
);
}
}
fn assert_i64_parity(pairs: &[(&str, &str)], seq: impl Fn(&str, &str) -> i64, got: &[i64]) {
assert_eq!(
got.len(),
pairs.len(),
"batch of {} pairs produced {} results",
pairs.len(),
got.len()
);
for (i, (a, b)) in pairs.iter().enumerate() {
assert_eq!(got[i], seq(a, b), "pair {i} ({a:?}, {b:?}) diverged");
}
}
#[test]
fn levenshtein_batch_empty_input_produces_empty_output() {
let opts = LevOptions::default();
let got = par_levenshtein_batch(&[], &opts);
assert!(got.is_empty());
}
#[test]
fn levenshtein_batch_a_single_item_matches_the_sequential_call() {
let opts = LevOptions::default();
let pairs = &all_pairs()[..1];
let got = par_levenshtein_batch(pairs, &opts);
assert_f64_parity(pairs, |a, b| levenshtein(a, b, &opts), &got);
}
#[test]
fn levenshtein_batch_matches_sequential_on_pathological_and_unicode_pairs() {
let opts = LevOptions::default();
let pairs = all_pairs();
let got = par_levenshtein_batch(&pairs, &opts);
assert_f64_parity(&pairs, |a, b| levenshtein(a, b, &opts), &got);
}
#[test]
fn levenshtein_batch_many_items_preserve_order_and_match_the_sequential_loop() {
let opts = LevOptions::default();
let pairs = many_pairs();
let got = par_levenshtein_batch(&pairs, &opts);
assert_f64_parity(&pairs, |a, b| levenshtein(a, b, &opts), &got);
}
#[test]
fn damerau_batch_empty_input_produces_empty_output() {
let opts = LevOptions::default();
let got = par_damerau_levenshtein_batch(&[], &opts);
assert!(got.is_empty());
}
#[test]
fn damerau_batch_a_single_item_matches_the_sequential_call() {
let opts = LevOptions::default();
let pairs = &all_pairs()[..1];
let got = par_damerau_levenshtein_batch(pairs, &opts);
assert_f64_parity(pairs, |a, b| damerau_levenshtein(a, b, &opts), &got);
}
#[test]
fn damerau_batch_matches_sequential_unrestricted() {
let opts = LevOptions {
restricted: false,
..LevOptions::default()
};
let pairs = many_pairs();
let got = par_damerau_levenshtein_batch(&pairs, &opts);
assert_f64_parity(&pairs, |a, b| damerau_levenshtein(a, b, &opts), &got);
}
#[test]
fn damerau_batch_matches_sequential_restricted() {
let opts = LevOptions {
restricted: true,
..LevOptions::default()
};
let pairs = many_pairs();
let got = par_damerau_levenshtein_batch(&pairs, &opts);
assert_f64_parity(&pairs, |a, b| damerau_levenshtein(a, b, &opts), &got);
}
#[test]
fn jaro_winkler_batch_empty_input_produces_empty_output() {
let opts = JwOptions::default();
let got = par_jaro_winkler_batch(&[], &opts);
assert!(got.is_empty());
}
#[test]
fn jaro_winkler_batch_a_single_item_matches_the_sequential_call() {
let opts = JwOptions::default();
let pairs = &all_pairs()[..1];
let got = par_jaro_winkler_batch(pairs, &opts);
assert_f64_parity(pairs, |a, b| jaro_winkler(a, b, &opts), &got);
}
#[test]
fn jaro_winkler_batch_matches_sequential_on_pathological_and_unicode_pairs() {
let opts = JwOptions::default();
let pairs = all_pairs();
let got = par_jaro_winkler_batch(&pairs, &opts);
assert_f64_parity(&pairs, |a, b| jaro_winkler(a, b, &opts), &got);
}
#[test]
fn jaro_winkler_batch_many_items_preserve_order_and_match_the_sequential_loop() {
let opts = JwOptions::default();
let pairs = many_pairs();
let got = par_jaro_winkler_batch(&pairs, &opts);
assert_f64_parity(&pairs, |a, b| jaro_winkler(a, b, &opts), &got);
}
#[test]
fn jaro_winkler_batch_respects_ignore_case() {
let opts = JwOptions {
ignore_case: true,
dj: None,
};
let pairs = [("A", "a"), ("X", "x"), ("AB", "ab"), ("MARTHA", "martha")];
let got = par_jaro_winkler_batch(&pairs, &opts);
assert_f64_parity(&pairs, |a, b| jaro_winkler(a, b, &opts), &got);
}
#[test]
fn dice_batch_empty_input_produces_empty_output() {
let got = par_dice_coefficient_batch(&[]);
assert!(got.is_empty());
}
#[test]
fn dice_batch_a_single_item_matches_the_sequential_call() {
let pairs = &all_pairs()[..1];
let got = par_dice_coefficient_batch(pairs);
assert_f64_parity(pairs, verbora_distance::dice_coefficient, &got);
}
#[test]
fn dice_batch_matches_sequential_on_pathological_and_unicode_pairs_including_nan() {
let pairs = all_pairs();
let got = par_dice_coefficient_batch(&pairs);
assert_f64_parity(&pairs, verbora_distance::dice_coefficient, &got);
}
#[test]
fn dice_batch_many_items_preserve_order_and_match_the_sequential_loop() {
let pairs = many_pairs();
let got = par_dice_coefficient_batch(&pairs);
assert_f64_parity(&pairs, verbora_distance::dice_coefficient, &got);
}
#[test]
fn hamming_batch_empty_input_produces_empty_output() {
let got = par_hamming_batch(&[], false);
assert!(got.is_empty());
}
#[test]
fn hamming_batch_a_single_item_matches_the_sequential_call() {
let pairs = &all_pairs()[..1];
let got = par_hamming_batch(pairs, false);
assert_i64_parity(pairs, |a, b| hamming(a, b, false), &got);
}
#[test]
fn hamming_batch_matches_sequential_on_pathological_and_unicode_pairs_including_mismatches() {
let pairs = all_pairs();
let got = par_hamming_batch(&pairs, false);
assert_i64_parity(&pairs, |a, b| hamming(a, b, false), &got);
}
#[test]
fn hamming_batch_many_items_preserve_order_and_match_the_sequential_loop() {
let pairs = many_pairs();
let got = par_hamming_batch(&pairs, false);
assert_i64_parity(&pairs, |a, b| hamming(a, b, false), &got);
}
#[test]
fn hamming_batch_respects_ignore_case() {
let pairs = [("ABC", "abc"), ("karolin", "KATHRIN")];
let got = par_hamming_batch(&pairs, true);
assert_i64_parity(&pairs, |a, b| hamming(a, b, true), &got);
}