mod extension;
mod pattern_splitter;
mod query_iterator;
pub use extension::{BidirectionalExtension, ExtensionState};
pub use pattern_splitter::{PatternPiece, PatternSplitter};
pub use query_iterator::{WallBreakerQuery, WallBreakerResult};
use crate::transducer::Algorithm;
use libdictenstein::substring::{BidirectionalDictionaryNode, SubstringDictionary};
use libdictenstein::Dictionary;
pub struct WallBreaker<'a, D>
where
D: Dictionary + SubstringDictionary,
D::Node: BidirectionalDictionaryNode,
<D::Node as crate::dictionary::DictionaryNode>::Unit: Into<u32>,
{
dictionary: &'a D,
max_distance: usize,
algorithm: Algorithm,
splitter: PatternSplitter,
}
impl<'a, D> WallBreaker<'a, D>
where
D: Dictionary + SubstringDictionary,
D::Node: BidirectionalDictionaryNode,
<D::Node as crate::dictionary::DictionaryNode>::Unit: Into<u32>,
{
pub fn new(dictionary: &'a D, max_distance: usize) -> Self {
Self::with_algorithm(dictionary, max_distance, Algorithm::Standard)
}
pub fn with_algorithm(dictionary: &'a D, max_distance: usize, algorithm: Algorithm) -> Self {
WallBreaker {
dictionary,
max_distance,
algorithm,
splitter: PatternSplitter::new(max_distance, algorithm),
}
}
pub fn query(&self, query: &str) -> WallBreakerQuery<'_, D> {
WallBreakerQuery::new(self.dictionary, query, self.max_distance, &self.splitter)
}
pub fn max_distance(&self) -> usize {
self.max_distance
}
pub fn algorithm(&self) -> Algorithm {
self.algorithm
}
pub fn set_max_distance(&mut self, max_distance: usize) {
self.max_distance = max_distance;
self.splitter = PatternSplitter::new(max_distance, self.algorithm);
}
pub fn set_algorithm(&mut self, algorithm: Algorithm) {
self.algorithm = algorithm;
self.splitter = PatternSplitter::new(self.max_distance, algorithm);
}
}
#[cfg(test)]
mod tests {
use super::*;
use libdictenstein::scdawg::Scdawg;
#[test]
fn test_wallbreaker_basic() {
let dict = Scdawg::<()>::from_terms(vec!["hello", "world", "help"]);
let wb = WallBreaker::new(&dict, 1);
let results: Vec<_> = wb.query("helo").collect();
assert!(!results.is_empty());
assert!(results.iter().any(|r| r.term == "hello"));
}
#[test]
fn test_wallbreaker_exact_match() {
let dict = Scdawg::<()>::from_terms(vec!["hello", "world"]);
let wb = WallBreaker::new(&dict, 0);
let results: Vec<_> = wb.query("hello").collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].term, "hello");
assert_eq!(results[0].distance, 0);
}
#[test]
fn test_wallbreaker_no_match() {
let dict = Scdawg::<()>::from_terms(vec!["hello", "world"]);
let wb = WallBreaker::new(&dict, 1);
let results: Vec<_> = wb.query("xyz").collect();
assert!(results.is_empty());
}
#[test]
fn test_wallbreaker_distance_2() {
let dict = Scdawg::<()>::from_terms(vec!["cathedral"]);
let wb = WallBreaker::new(&dict, 2);
let results: Vec<_> = wb.query("cathedrel").collect();
assert!(results.iter().any(|r| r.term == "cathedral"));
}
#[test]
fn test_wallbreaker_multiple_terms() {
let terms = vec![
"cathedral",
"category",
"catering",
"catastrophe",
"catalog",
];
let dict = Scdawg::<()>::from_terms(terms);
let wb = WallBreaker::new(&dict, 2);
let results: Vec<_> = wb.query("cathedrel").collect();
assert!(results.iter().any(|r| r.term == "cathedral"));
let results: Vec<_> = wb.query("caterng").collect();
assert!(results.iter().any(|r| r.term == "catering"));
}
#[test]
fn test_wallbreaker_with_algorithm() {
let dict = Scdawg::<()>::from_terms(vec!["hello", "world", "help"]);
let wb_std = WallBreaker::with_algorithm(&dict, 1, Algorithm::Standard);
let wb_trans = WallBreaker::with_algorithm(&dict, 1, Algorithm::Transposition);
let wb_ms = WallBreaker::with_algorithm(&dict, 1, Algorithm::MergeAndSplit);
assert!(matches!(wb_std.algorithm(), Algorithm::Standard));
assert!(matches!(wb_trans.algorithm(), Algorithm::Transposition));
assert!(matches!(wb_ms.algorithm(), Algorithm::MergeAndSplit));
}
#[test]
fn test_wallbreaker_algorithm_getter() {
let dict = Scdawg::<()>::from_terms(vec!["test"]);
let wb = WallBreaker::new(&dict, 1);
assert!(matches!(wb.algorithm(), Algorithm::Standard));
let wb = WallBreaker::with_algorithm(&dict, 1, Algorithm::Transposition);
assert!(matches!(wb.algorithm(), Algorithm::Transposition));
}
#[test]
fn test_wallbreaker_set_algorithm() {
let dict = Scdawg::<()>::from_terms(vec!["test"]);
let mut wb = WallBreaker::new(&dict, 2);
assert!(matches!(wb.algorithm(), Algorithm::Standard));
wb.set_algorithm(Algorithm::Transposition);
assert!(matches!(wb.algorithm(), Algorithm::Transposition));
wb.set_algorithm(Algorithm::MergeAndSplit);
assert!(matches!(wb.algorithm(), Algorithm::MergeAndSplit));
}
#[test]
fn test_wallbreaker_set_max_distance_preserves_algorithm() {
let dict = Scdawg::<()>::from_terms(vec!["test"]);
let mut wb = WallBreaker::with_algorithm(&dict, 2, Algorithm::Transposition);
wb.set_max_distance(4);
assert!(matches!(wb.algorithm(), Algorithm::Transposition));
assert_eq!(wb.max_distance(), 4);
}
#[test]
fn test_wallbreaker_transposition_finds_matches() {
let dict = Scdawg::<()>::from_terms(vec!["hello", "world", "help"]);
let wb = WallBreaker::with_algorithm(&dict, 1, Algorithm::Transposition);
let results: Vec<_> = wb.query("helo").collect();
assert!(!results.is_empty());
}
#[test]
fn test_wallbreaker_merge_and_split_finds_matches() {
let dict = Scdawg::<()>::from_terms(vec!["hello", "world", "help"]);
let wb = WallBreaker::with_algorithm(&dict, 1, Algorithm::MergeAndSplit);
let results: Vec<_> = wb.query("helo").collect();
assert!(!results.is_empty());
}
}