use std::collections::HashSet;
use crate::distance::{
create_memo_cache, merge_and_split_distance, standard_distance, transposition_distance,
};
use crate::transducer::Algorithm;
use libdictenstein::substring::{BidirectionalDictionaryNode, SubstringDictionary};
use libdictenstein::Dictionary;
use super::extension::BidirectionalExtension;
use super::pattern_splitter::{PatternPiece, PatternSplitter};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct WallBreakerResult {
pub term: String,
pub distance: usize,
}
impl WallBreakerResult {
pub fn new(term: String, distance: usize) -> Self {
WallBreakerResult { term, distance }
}
}
pub struct WallBreakerQuery<'a, D>
where
D: Dictionary + SubstringDictionary,
D::Node: BidirectionalDictionaryNode,
<D::Node as crate::dictionary::DictionaryNode>::Unit: Into<u32>,
{
dictionary: &'a D,
query: String,
max_distance: usize,
algorithm: Algorithm,
pieces: Vec<PatternPiece>,
current_piece_idx: usize,
current_results: Vec<WallBreakerResult>,
result_idx: usize,
seen_terms: HashSet<String>,
exhausted: bool,
}
impl<'a, D> WallBreakerQuery<'a, D>
where
D: Dictionary + SubstringDictionary,
D::Node: BidirectionalDictionaryNode,
<D::Node as crate::dictionary::DictionaryNode>::Unit: Into<u32>,
{
pub fn new(
dictionary: &'a D,
query: &str,
max_distance: usize,
splitter: &PatternSplitter,
) -> Self {
let pieces = splitter.split(query);
let algorithm = splitter.algorithm();
WallBreakerQuery {
dictionary,
query: query.to_string(),
max_distance,
algorithm,
pieces,
current_piece_idx: 0,
current_results: Vec::new(),
result_idx: 0,
seen_terms: HashSet::new(),
exhausted: false,
}
}
fn compute_distance(&self, s1: &str, s2: &str) -> usize {
match self.algorithm {
Algorithm::Standard => standard_distance(s1, s2),
Algorithm::Transposition => transposition_distance(s1, s2),
Algorithm::MergeAndSplit => {
let cache = create_memo_cache();
merge_and_split_distance(s1, s2, &cache)
}
}
}
fn process_next_piece(&mut self) -> bool {
while self.current_piece_idx < self.pieces.len() {
let piece = &self.pieces[self.current_piece_idx];
self.current_piece_idx += 1;
if piece.is_empty() {
continue;
}
let substring_matches = self.dictionary.find_exact_substring(&piece.content);
for match_info in &substring_matches {
let extension = BidirectionalExtension::new(
match_info,
&self.query,
piece.start_offset,
piece.end_offset,
self.max_distance,
);
let extensions = extension.extend();
for (term, _distance) in extensions {
if self.seen_terms.contains(&term) {
continue;
}
let actual_distance = self.compute_distance(&self.query, &term);
if actual_distance <= self.max_distance {
self.seen_terms.insert(term.clone());
self.current_results
.push(WallBreakerResult::new(term, actual_distance));
}
}
}
if !self.current_results.is_empty() {
self.result_idx = 0;
return true;
}
}
false
}
}
impl<'a, D> Iterator for WallBreakerQuery<'a, D>
where
D: Dictionary + SubstringDictionary,
D::Node: BidirectionalDictionaryNode,
<D::Node as crate::dictionary::DictionaryNode>::Unit: Into<u32>,
{
type Item = WallBreakerResult;
fn next(&mut self) -> Option<Self::Item> {
if self.exhausted {
return None;
}
loop {
if self.result_idx < self.current_results.len() {
let result = self.current_results[self.result_idx].clone();
self.result_idx += 1;
return Some(result);
}
self.current_results.clear();
self.result_idx = 0;
if !self.process_next_piece() {
self.exhausted = true;
return None;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_levenshtein_distance() {
assert_eq!(standard_distance("", ""), 0);
assert_eq!(standard_distance("abc", ""), 3);
assert_eq!(standard_distance("", "abc"), 3);
assert_eq!(standard_distance("abc", "abc"), 0);
assert_eq!(standard_distance("kitten", "sitting"), 3);
assert_eq!(standard_distance("saturday", "sunday"), 3);
assert_eq!(standard_distance("hello", "helo"), 1);
assert_eq!(standard_distance("cathedral", "cathedrel"), 1);
}
#[test]
fn test_transposition_distance() {
assert_eq!(transposition_distance("ab", "ba"), 1);
assert_eq!(transposition_distance("test", "tset"), 1);
assert_eq!(transposition_distance("abc", "acb"), 1);
}
#[test]
fn test_merge_and_split_distance() {
let cache = create_memo_cache();
assert_eq!(merge_and_split_distance("", "", &cache), 0);
assert_eq!(merge_and_split_distance("abc", "abc", &cache), 0);
assert_eq!(merge_and_split_distance("test", "best", &cache), 1);
}
#[test]
fn test_wallbreaker_result() {
let result = WallBreakerResult::new("hello".to_string(), 1);
assert_eq!(result.term, "hello");
assert_eq!(result.distance, 1);
}
}