use crate::transducer::{Algorithm, AutomatonZipper, Candidate, IntersectionZipper, StatePool};
use libdictenstein::zipper::DictZipper;
use std::collections::VecDeque;
pub struct ZipperQueryIterator<D>
where
D: DictZipper<Unit = u8>,
{
queue: VecDeque<IntersectionZipper<D>>,
pool: StatePool,
}
impl<D> ZipperQueryIterator<D>
where
D: DictZipper<Unit = u8>,
{
pub fn new(dict_zipper: D, query: &str, max_distance: usize, algorithm: Algorithm) -> Self {
let automaton = AutomatonZipper::new(query.as_bytes(), max_distance, algorithm);
let intersection = IntersectionZipper::new(dict_zipper, automaton);
let mut queue = VecDeque::new();
queue.push_back(intersection);
ZipperQueryIterator {
queue,
pool: StatePool::new(),
}
}
}
impl<D> Iterator for ZipperQueryIterator<D>
where
D: DictZipper<Unit = u8>,
{
type Item = Candidate;
fn next(&mut self) -> Option<Self::Item> {
while let Some(intersection) = self.queue.pop_front() {
if intersection.is_match() {
if let Some(distance) = intersection.distance() {
let candidate = Candidate {
term: intersection.term(),
distance,
};
let children: Vec<_> = intersection.children(&mut self.pool).collect();
for (_label, child) in children {
if child.is_viable() {
self.queue.push_back(child);
}
}
return Some(candidate);
}
}
let children: Vec<_> = intersection.children(&mut self.pool).collect();
for (_label, child) in children {
if child.is_viable() {
self.queue.push_back(child);
}
}
}
None
}
}
#[cfg(all(test, feature = "pathmap-backend"))]
mod tests {
use super::*;
use libdictenstein::pathmap::zipper::PathMapZipper;
use libdictenstein::pathmap::PathMapDictionary;
#[test]
fn test_exact_match() {
let dict = PathMapDictionary::<()>::new();
dict.insert("cat");
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let iter = ZipperQueryIterator::new(dict_zipper, "cat", 0, Algorithm::Standard);
let results: Vec<_> = iter.collect();
assert_eq!(results.len(), 1);
assert_eq!(results[0].term, "cat");
assert_eq!(results[0].distance, 0);
}
#[test]
fn test_fuzzy_match() {
let dict = PathMapDictionary::<()>::new();
dict.insert("cat");
dict.insert("car");
dict.insert("dog");
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let iter = ZipperQueryIterator::new(dict_zipper, "cat", 1, Algorithm::Standard);
let results: Vec<_> = iter.collect();
assert!(results.iter().any(|c| c.term == "cat" && c.distance == 0));
assert!(results.iter().any(|c| c.term == "car" && c.distance == 1));
assert!(!results.iter().any(|c| c.term == "dog"));
}
#[test]
fn test_multiple_distances() {
let dict = PathMapDictionary::<()>::new();
dict.insert("cat");
dict.insert("at");
dict.insert("ca");
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let iter = ZipperQueryIterator::new(dict_zipper, "cat", 1, Algorithm::Standard);
let results: Vec<_> = iter.collect();
assert_eq!(results.len(), 3);
assert!(results.iter().any(|c| c.term == "cat" && c.distance == 0));
assert!(results.iter().any(|c| c.term == "at"));
assert!(results.iter().any(|c| c.term == "ca" && c.distance == 0));
}
#[test]
fn test_empty_dictionary() {
let dict = PathMapDictionary::<()>::new();
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let iter = ZipperQueryIterator::new(dict_zipper, "cat", 1, Algorithm::Standard);
let results: Vec<_> = iter.collect();
assert_eq!(results.len(), 0);
}
#[test]
fn test_no_matches_within_distance() {
let dict = PathMapDictionary::<()>::new();
dict.insert("cat");
dict.insert("car");
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let iter = ZipperQueryIterator::new(dict_zipper, "dog", 1, Algorithm::Standard);
let results: Vec<_> = iter.collect();
assert_eq!(results.len(), 0);
}
#[test]
fn test_transposition_algorithm() {
let dict = PathMapDictionary::<()>::new();
dict.insert("abcd");
dict.insert("bacd");
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let iter = ZipperQueryIterator::new(dict_zipper, "abcd", 1, Algorithm::Transposition);
let results: Vec<_> = iter.collect();
assert!(results.iter().any(|c| c.term == "abcd"));
assert!(results.iter().any(|c| c.term == "bacd"));
}
#[test]
fn test_larger_dictionary() {
let dict = PathMapDictionary::<()>::new();
let words = vec![
"cat", "car", "card", "care", "careful", "dog", "door", "dot", "test", "testing",
"tester",
];
for word in &words {
dict.insert(word);
}
let dict_zipper = PathMapZipper::new_from_dict(&dict);
let iter = ZipperQueryIterator::new(dict_zipper, "car", 1, Algorithm::Standard);
let results: Vec<_> = iter.collect();
assert!(results.iter().any(|c| c.term == "car"));
assert!(results.iter().any(|c| c.term == "cat"));
assert!(results.iter().any(|c| c.term == "card"));
assert!(results.iter().any(|c| c.term == "care"));
assert!(results.len() >= 4);
}
}