use super::query_result::QueryResult;
use super::transition::{initial_state, transition_state_pooled};
use super::{
Algorithm, Intersection, StatePool, SubstitutionPolicy, SubstitutionPolicyFor, Unrestricted,
};
use libdictenstein::{CharUnit, DictionaryNode};
use std::collections::VecDeque;
use std::marker::PhantomData;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Candidate {
pub term: String,
pub distance: usize,
}
pub struct QueryIterator<
N: DictionaryNode,
R: QueryResult = String,
P: SubstitutionPolicy = Unrestricted,
> {
pending: VecDeque<Box<Intersection<N>>>,
query: Vec<N::Unit>,
max_distance: usize,
algorithm: Algorithm,
policy: P, finished: bool,
state_pool: StatePool, substring_mode: bool, _result_type: PhantomData<R>, }
impl<N: DictionaryNode, R: QueryResult> QueryIterator<N, R, Unrestricted> {
pub fn new(root: N, query: String, max_distance: usize, algorithm: Algorithm) -> Self {
Self::with_substring_mode(root, query, max_distance, algorithm, false)
}
pub fn with_substring_mode(
root: N,
query: String,
max_distance: usize,
algorithm: Algorithm,
substring_mode: bool,
) -> Self {
Self::with_policy_and_substring(
root,
query,
max_distance,
algorithm,
Unrestricted,
substring_mode,
)
}
}
impl<N: DictionaryNode, R: QueryResult, P: SubstitutionPolicy + SubstitutionPolicyFor<N::Unit>>
QueryIterator<N, R, P>
{
pub fn with_policy(
root: N,
query: String,
max_distance: usize,
algorithm: Algorithm,
policy: P,
) -> Self {
Self::with_policy_and_substring(root, query, max_distance, algorithm, policy, false)
}
pub fn with_policy_and_substring(
root: N,
query: String,
max_distance: usize,
algorithm: Algorithm,
policy: P,
substring_mode: bool,
) -> Self {
let query_units = N::Unit::from_str(&query);
let initial = initial_state(query_units.len(), max_distance, algorithm);
let mut pending = VecDeque::new();
pending.push_back(Box::new(Intersection::new(root, initial)));
Self {
pending,
query: query_units,
max_distance,
algorithm,
policy,
finished: false,
state_pool: StatePool::new(), substring_mode,
_result_type: PhantomData, }
}
fn advance(&mut self) -> Option<R> {
while let Some(intersection) = self.pending.pop_front() {
if intersection.is_final() {
let distance = if self.substring_mode {
intersection.state.min_distance().unwrap_or(usize::MAX)
} else {
intersection
.state
.infer_distance(self.query.len())
.unwrap_or(usize::MAX)
};
if distance <= self.max_distance {
let term = intersection.term();
self.queue_children(&intersection);
return Some(R::from_match(term, distance));
} else {
self.queue_children(&intersection);
}
} else {
self.queue_children(&intersection);
}
}
self.finished = true;
None
}
fn queue_children(&mut self, intersection: &Intersection<N>) {
for (label, child_node) in intersection.node.edges() {
if let Some(next_state) = transition_state_pooled(
&intersection.state,
&mut self.state_pool, self.policy, label,
&self.query,
self.max_distance,
self.algorithm,
self.substring_mode, ) {
let parent_path = intersection.label.map(|current_label| {
Box::new(super::intersection::PathNode::new(
current_label,
intersection.parent.clone(), ))
});
let child = Box::new(Intersection::with_parent(
label,
child_node,
next_state,
parent_path, ));
self.pending.push_back(child);
}
}
}
}
impl<N: DictionaryNode, R: QueryResult, P: SubstitutionPolicy + SubstitutionPolicyFor<N::Unit>>
Iterator for QueryIterator<N, R, P>
{
type Item = R;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
None
} else {
self.advance()
}
}
}
pub type StringQueryIterator<N> = QueryIterator<N, String>;
pub type CandidateIterator<N> = QueryIterator<N, Candidate>;
#[cfg(test)]
mod tests {
use super::*;
use libdictenstein::double_array_trie::DoubleArrayTrie;
use libdictenstein::Dictionary;
#[test]
fn test_query_exact_match() {
let dict = DoubleArrayTrie::from_terms(vec!["test"]);
let query: QueryIterator<_, String> =
QueryIterator::new(dict.root(), "test".to_string(), 0, Algorithm::Standard);
let result: Vec<_> = query.collect();
assert_eq!(result, vec!["test"]);
}
#[test]
fn test_query_with_distance() {
let dict = DoubleArrayTrie::from_terms(vec!["test", "best", "rest", "testing"]);
let query = QueryIterator::new(dict.root(), "test".to_string(), 1, Algorithm::Standard);
let results: Vec<_> = query.collect();
assert!(results.contains(&"test".to_string()));
assert!(results.contains(&"best".to_string()));
assert!(results.contains(&"rest".to_string()));
}
#[test]
fn test_candidate_iterator() {
let dict = DoubleArrayTrie::from_terms(vec!["test", "best"]);
let query = CandidateIterator::new(dict.root(), "test".to_string(), 1, Algorithm::Standard);
let candidates: Vec<_> = query.collect();
assert!(candidates
.iter()
.any(|c| c.term == "test" && c.distance == 0));
assert!(candidates
.iter()
.any(|c| c.term == "best" && c.distance == 1));
}
#[test]
fn test_empty_query() {
let dict = DoubleArrayTrie::from_terms(vec!["test"]);
let query = QueryIterator::new(dict.root(), "".to_string(), 0, Algorithm::Standard);
let results: Vec<_> = query.collect();
assert!(results.is_empty() || results.contains(&"".to_string()));
}
}