use crate::{Algorithm, Result};
#[derive(Default)]
pub struct Hamming {
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());
}
}
}