• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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