1 //! LIG3 similarity 2 use super::hamming::Hamming; 3 use super::levenshtein::Levenshtein; 4 use crate::{Algorithm, Result}; 5 use core::hash::Hash; 6 7 /// [LIG3 similarity] is a normalization of [`Hamming`] by [`Levenshtein`]. 8 /// 9 /// [LIG3 similarity]: https://github.com/chrislit/abydos/blob/master/abydos/distance/_lig3.py 10 pub struct LIG3 { 11 /// Algorithm instance to use for calculating Levenshtein distance. 12 pub levenshtein: Levenshtein, 13 14 /// Algorithm instance to use for calculating Hamming similarity. 15 pub hamming: Hamming, 16 } 17 18 impl Default for LIG3 { default() -> Self19 fn default() -> Self { 20 Self { 21 levenshtein: Levenshtein::default(), 22 #[allow(clippy::needless_update)] 23 hamming: Hamming { 24 truncate: false, 25 ..Default::default() 26 }, 27 } 28 } 29 } 30 31 impl Algorithm<f64> for LIG3 { for_vec<E>(&self, s1: &[E], s2: &[E]) -> Result<f64> where E: Eq + Hash,32 fn for_vec<E>(&self, s1: &[E], s2: &[E]) -> Result<f64> 33 where 34 E: Eq + Hash, 35 { 36 let lev_res = self.levenshtein.for_vec(s1, s2); 37 let lev = lev_res.dist(); 38 let ham = self.hamming.for_vec(s1, s2).sim(); 39 let res = if lev == 0 && ham == 0 { 40 1. 41 } else { 42 (2 * ham) as f64 / (2 * ham + lev) as f64 43 }; 44 Result { 45 abs: res, 46 is_distance: false, 47 max: 1.0, 48 len1: lev_res.len1, 49 len2: lev_res.len2, 50 } 51 } 52 } 53 54 #[cfg(test)] 55 mod tests { 56 use crate::str::lig3; 57 use assert2::assert; 58 use rstest::rstest; 59 is_close(a: f64, b: f64) -> bool60 fn is_close(a: f64, b: f64) -> bool { 61 (a - b).abs() < 1E-5 62 } 63 64 #[rstest] 65 // parity with abydos 66 #[case("cat", "hat", 0.8)] 67 #[case("Niall", "Neil", 0.5714285714285714)] 68 #[case("aluminum", "Catalan", 0.0)] 69 #[case("ATCG", "TAGC", 0.0)] 70 #[case("Glavin", "Glawyn", 0.8)] 71 #[case("Williams", "Vylliems", 0.7692307692307693)] 72 #[case("Lewis", "Louis", 0.75)] 73 #[case("Alex", "Alexander", 0.6153846153846154)] 74 #[case("Wild", "Wildsmith", 0.6153846153846154)] 75 #[case("Bram", "Bramberley", 0.5714285714285714)] function_str(#[case] s1: &str, #[case] s2: &str, #[case] exp: f64)76 fn function_str(#[case] s1: &str, #[case] s2: &str, #[case] exp: f64) { 77 let act = lig3(s1, s2); 78 let ok = is_close(act, exp); 79 assert!(ok, "lig3({}, {}) is {}, not {}", s1, s2, act, exp); 80 } 81 } 82