matchete 0.0.36

A fast and flexible matcher for comparing and diffing data structures.
Documentation
use {
    hashish::HashMap,
    crate::{
        assessor::{Resembler, Resemblance, Assessment},
        prelude::string::utils::{edit_distance, keyboard::Layout},
    }
};
use core::cmp::{max, min};

#[derive(PartialEq)]
pub struct Keyboard {
    layout: HashMap<char, Vec<char>>,
}

impl Default for Keyboard {
    fn default() -> Self {
        Self {
            layout: Layout::Qwerty.get_layout(),
        }
    }
}

impl Keyboard {
    pub fn new(layout_type: Layout) -> Self {
        Self {
            layout: layout_type.get_layout(),
        }
    }
}

impl Resembler<String, String, ()> for Keyboard {
    fn assessment(&mut self, query: &String, candidate: &String) -> Assessment<()> {
        if query == candidate {
            return Assessment { resemblance: Resemblance::Perfect, errors: vec![] };
        }

        let query_chars: Vec<char> = query.to_lowercase().chars().collect();
        let candidate_chars: Vec<char> = candidate.to_lowercase().chars().collect();

        if (query_chars.len() as isize - candidate_chars.len() as isize).abs() > 2 {
            return Assessment { resemblance: Resemblance::Disparity, errors: vec![] };
        }

        let distance = edit_distance(query, candidate);
        if distance > 3 {
            return Assessment { resemblance: Resemblance::Disparity, errors: vec![] };
        }

        let mut adjacent_count = 0;
        let max_comparisons = min(query_chars.len(), candidate_chars.len());
        for i in 0..max_comparisons {
            if query_chars[i] == candidate_chars[i] { continue; }
            if let Some(neighbors) = self.layout.get(&query_chars[i]) {
                if neighbors.contains(&candidate_chars[i]) { adjacent_count += 1; }
            }
        }

        let differing_chars = distance;
        if differing_chars == 0 { return Assessment { resemblance: Resemblance::Perfect, errors: vec![] }; }

        let keyboard_factor = adjacent_count as f64 / differing_chars as f64;
        let length_similarity = 1.0 - ((query_chars.len() as isize - candidate_chars.len() as isize).abs() as f64 / max(query_chars.len(), candidate_chars.len()) as f64);
        let base_score = 1.0 - (distance as f64 / max(query_chars.len(), candidate_chars.len()) as f64);
        let score = base_score * (1.0 + 0.5 * keyboard_factor) * length_similarity;

        let resemblance = if score >= 1.0 {
            Resemblance::Perfect
        } else if score > 0.0 {
            Resemblance::Partial(score)
        } else {
            Resemblance::Disparity
        };

        Assessment { resemblance, errors: vec![] }
    }
}