use super::intersection::PathNode;
use super::intersection_f64::IntersectionF64;
use super::transition_f64::{initial_state_f64, transition_state_pooled_f64};
use super::{
Algorithm, OperationCostsF64, StatePoolF64, SubstitutionPolicy, SubstitutionPolicyFor,
Unrestricted,
};
use libdictenstein::{CharUnit, DictionaryNode};
use std::collections::VecDeque;
use std::marker::PhantomData;
#[derive(Debug, Clone, PartialEq)]
pub struct CandidateF64 {
pub term: String,
pub distance: f64,
}
pub trait QueryResultF64: Sized {
fn from_match(term: String, distance: f64) -> Self;
}
impl QueryResultF64 for String {
#[inline]
fn from_match(term: String, _distance: f64) -> Self {
term
}
}
impl QueryResultF64 for CandidateF64 {
#[inline]
fn from_match(term: String, distance: f64) -> Self {
CandidateF64 { term, distance }
}
}
pub struct QueryIteratorF64<
N: DictionaryNode,
R: QueryResultF64 = String,
P: SubstitutionPolicy = Unrestricted,
> {
pending: VecDeque<Box<IntersectionF64<N>>>,
query: Vec<N::Unit>,
max_cost: f64,
algorithm: Algorithm,
costs: OperationCostsF64,
policy: P,
finished: bool,
state_pool: StatePoolF64,
substring_mode: bool,
_result_type: PhantomData<R>,
}
impl<N: DictionaryNode, R: QueryResultF64> QueryIteratorF64<N, R, Unrestricted> {
pub fn new(
root: N,
query: String,
max_cost: f64,
algorithm: Algorithm,
costs: OperationCostsF64,
) -> Self {
Self::with_substring_mode(root, query, max_cost, algorithm, costs, false)
}
pub fn with_substring_mode(
root: N,
query: String,
max_cost: f64,
algorithm: Algorithm,
costs: OperationCostsF64,
substring_mode: bool,
) -> Self {
Self::with_policy_and_substring(
root,
query,
max_cost,
algorithm,
costs,
Unrestricted,
substring_mode,
)
}
}
impl<
N: DictionaryNode,
R: QueryResultF64,
P: SubstitutionPolicy + SubstitutionPolicyFor<N::Unit>,
> QueryIteratorF64<N, R, P>
{
pub fn with_policy(
root: N,
query: String,
max_cost: f64,
algorithm: Algorithm,
costs: OperationCostsF64,
policy: P,
) -> Self {
Self::with_policy_and_substring(root, query, max_cost, algorithm, costs, policy, false)
}
pub fn with_policy_and_substring(
root: N,
query: String,
max_cost: f64,
algorithm: Algorithm,
costs: OperationCostsF64,
policy: P,
substring_mode: bool,
) -> Self {
let query_units = N::Unit::from_str(&query);
let initial = initial_state_f64(query_units.len(), max_cost, algorithm, &costs);
let mut pending = VecDeque::new();
pending.push_back(Box::new(IntersectionF64::new(root, initial)));
Self {
pending,
query: query_units,
max_cost,
algorithm,
costs,
policy,
finished: false,
state_pool: StatePoolF64::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(f64::INFINITY)
} else {
intersection
.state
.infer_distance(self.query.len())
.unwrap_or(f64::INFINITY)
};
if distance <= self.max_cost + 1e-9 {
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: &IntersectionF64<N>) {
for (label, child_node) in intersection.node.edges() {
if let Some(next_state) = transition_state_pooled_f64(
&intersection.state,
&mut self.state_pool,
self.policy,
label,
&self.query,
self.max_cost,
self.algorithm,
&self.costs,
self.substring_mode,
) {
let parent_path = intersection.label.map(|current_label| {
Box::new(PathNode::new(current_label, intersection.parent.clone()))
});
let child = Box::new(IntersectionF64::with_parent(
label,
child_node,
next_state,
parent_path,
));
self.pending.push_back(child);
}
}
}
}
impl<
N: DictionaryNode,
R: QueryResultF64,
P: SubstitutionPolicy + SubstitutionPolicyFor<N::Unit>,
> Iterator for QueryIteratorF64<N, R, P>
{
type Item = R;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
None
} else {
self.advance()
}
}
}
pub type StringQueryIteratorF64<N> = QueryIteratorF64<N, String>;
pub type CandidateIteratorF64<N> = QueryIteratorF64<N, CandidateF64>;
#[cfg(test)]
mod tests {
use super::*;
use libdictenstein::double_array_trie::DoubleArrayTrie;
use libdictenstein::Dictionary;
const EPSILON: f64 = 1e-9;
#[test]
fn test_query_exact_match() {
let dict = DoubleArrayTrie::from_terms(vec!["test"]);
let costs = OperationCostsF64::standard();
let query: QueryIteratorF64<_, String> = QueryIteratorF64::new(
dict.root(),
"test".to_string(),
0.0,
Algorithm::Standard,
costs,
);
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 costs = OperationCostsF64::standard();
let query = QueryIteratorF64::new(
dict.root(),
"test".to_string(),
1.0,
Algorithm::Standard,
costs,
);
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_f64() {
let dict = DoubleArrayTrie::from_terms(vec!["test", "best"]);
let costs = OperationCostsF64::standard();
let query = CandidateIteratorF64::new(
dict.root(),
"test".to_string(),
1.0,
Algorithm::Standard,
costs,
);
let candidates: Vec<_> = query.collect();
assert!(candidates
.iter()
.any(|c| c.term == "test" && c.distance.abs() < EPSILON));
assert!(candidates
.iter()
.any(|c| c.term == "best" && (c.distance - 1.0).abs() < EPSILON));
}
#[test]
fn test_custom_costs() {
let dict = DoubleArrayTrie::from_terms(vec!["abc", "axc"]);
let costs = OperationCostsF64::custom(2.0, 1.0, 1.0, 1.0, 1.0, 1.0);
let query = CandidateIteratorF64::new(
dict.root(),
"abc".to_string(),
1.5, Algorithm::Standard,
costs,
);
let candidates: Vec<_> = query.collect();
assert!(candidates.iter().any(|c| c.term == "abc"));
assert!(!candidates.iter().any(|c| c.term == "axc"));
}
#[test]
fn test_typo_friendly_transposition() {
let dict = DoubleArrayTrie::from_terms(vec!["the", "teh"]);
let costs = OperationCostsF64::typo_friendly();
let query = CandidateIteratorF64::new(
dict.root(),
"the".to_string(),
1.0, Algorithm::Transposition,
costs,
);
let candidates: Vec<_> = query.collect();
assert!(candidates
.iter()
.any(|c| c.term == "the" && c.distance.abs() < EPSILON));
assert!(candidates
.iter()
.any(|c| c.term == "teh" && (c.distance - 0.5).abs() < EPSILON));
}
#[test]
fn test_standard_costs_equivalent_to_integer() {
let dict = DoubleArrayTrie::from_terms(vec!["cat", "car", "bat", "bar"]);
let costs = OperationCostsF64::standard();
let query = CandidateIteratorF64::new(
dict.root(),
"cat".to_string(),
1.0,
Algorithm::Standard,
costs,
);
let candidates: Vec<_> = query.collect();
assert!(candidates.iter().any(|c| c.term == "cat"));
assert!(candidates.iter().any(|c| c.term == "car"));
assert!(candidates.iter().any(|c| c.term == "bat"));
assert!(!candidates.iter().any(|c| c.term == "bar"));
}
#[test]
fn test_empty_query() {
let dict = DoubleArrayTrie::from_terms(vec!["a", "ab"]);
let costs = OperationCostsF64::standard();
let query =
CandidateIteratorF64::new(dict.root(), "".to_string(), 1.0, Algorithm::Standard, costs);
let candidates: Vec<_> = query.collect();
assert!(candidates.iter().any(|c| c.term == "a"));
assert!(!candidates.iter().any(|c| c.term == "ab"));
}
}