| //! Hamming distance |
| use crate::{Algorithm, Result}; |
| |
| /// [Hamming distance] is the number of positions at which the corresponding symbols are different. |
| /// |
| /// [Hamming distance]: https://en.wikipedia.org/wiki/Hamming_distance |
| #[derive(Default)] |
| pub struct Hamming { |
| /// If false (default), the longer strings is truncated to the same length |
| /// as the shorter one. |
| pub truncate: bool, |
| } |
| |
| impl Algorithm<usize> for Hamming { |
| fn for_iter<C, E>(&self, mut s1: C, mut s2: C) -> Result<usize> |
| where |
| C: Iterator<Item = E>, |
| E: Eq, |
| { |
| let mut result = 0; |
| let mut l1 = 0; |
| let mut l2 = 0; |
| loop { |
| match (s1.next(), s2.next()) { |
| (Some(c1), Some(c2)) => { |
| l1 += 1; |
| l2 += 1; |
| if c1 != c2 { |
| result += 1; |
| } |
| } |
| (Some(_), None) => { |
| l1 += 1; |
| if !self.truncate { |
| result += 1; |
| } |
| } |
| (None, Some(_)) => { |
| l2 += 1; |
| if !self.truncate { |
| result += 1; |
| } |
| } |
| (None, None) => { |
| break; |
| } |
| } |
| } |
| Result { |
| abs: result, |
| is_distance: true, |
| max: l1.max(l2), |
| len1: l1, |
| len2: l2, |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| #![allow(clippy::float_cmp)] |
| |
| use super::{Algorithm, Hamming}; |
| use crate::str::hamming; |
| use assert2::assert; |
| use proptest::prelude::*; |
| use rstest::rstest; |
| |
| #[rstest] |
| #[case("", "", 0)] |
| #[case("", "\0", 1)] |
| #[case("", "abc", 3)] |
| #[case("abc", "", 3)] |
| #[case("sitting", "sitting", 0)] |
| #[case("abcdefg", "hijklmn", 7)] |
| #[case("karolin", "kathrin", 3)] |
| #[case("hello", "world", 4)] |
| #[case("Rust", "rust", 1)] |
| #[case("hi mark", "hi markus", 2)] |
| fn function_str(#[case] s1: &str, #[case] s2: &str, #[case] exp: usize) { |
| assert!(hamming(s1, s2) == exp); |
| } |
| |
| #[test] |
| fn default_struct_result() { |
| let r = Hamming::default().for_str("Rust", "rust"); |
| assert!(r.dist() == 1); |
| assert!(r.sim() == 3); |
| assert!(r.ndist() == 0.25); |
| } |
| |
| #[test] |
| fn truncate() { |
| let a = Hamming { truncate: true }; |
| assert!(a.for_str("hi mark", "hi markus").val() == 0); |
| assert!(a.for_str("Hi mark", "hi markus").val() == 1); |
| } |
| |
| proptest! { |
| #[test] |
| fn prop(s1 in ".*", s2 in ".*") { |
| let res = hamming(&s1, &s2); |
| let res2 = hamming(&s2, &s1); |
| prop_assert_eq!(res, res2); |
| prop_assert!(res <= s1.len() || res <= s2.len()); |
| } |
| } |
| } |