use crate::transducer::transition::transition_state_pooled;
use crate::transducer::{Algorithm, Position, State, StatePool, Unrestricted};
use std::sync::Arc;
#[derive(Clone)]
pub struct AutomatonZipper {
state: State,
query: Arc<Vec<u8>>,
max_distance: usize,
algorithm: Algorithm,
}
impl AutomatonZipper {
pub fn new(query: &[u8], max_distance: usize, algorithm: Algorithm) -> Self {
let mut state = State::new();
for distance in 0..=max_distance {
state.insert(Position::new(0, distance), algorithm, query.len());
}
AutomatonZipper {
state,
query: Arc::new(query.to_vec()),
max_distance,
algorithm,
}
}
fn with_state(
state: State,
query: Arc<Vec<u8>>,
max_distance: usize,
algorithm: Algorithm,
) -> Self {
AutomatonZipper {
state,
query,
max_distance,
algorithm,
}
}
#[inline]
pub fn transition(&self, dict_char: u8, pool: &mut StatePool) -> Option<Self> {
transition_state_pooled(
&self.state,
pool,
Unrestricted, dict_char,
&self.query,
self.max_distance,
self.algorithm,
false, )
.map(|next_state| {
AutomatonZipper::with_state(
next_state,
Arc::clone(&self.query),
self.max_distance,
self.algorithm,
)
})
}
pub fn min_distance_accepting(&self) -> Option<usize> {
let query_len = self.query.len();
let accepting_positions: Vec<_> = self
.state
.positions()
.iter()
.filter(|p| p.term_index == query_len && !p.is_special)
.collect();
if accepting_positions.is_empty() {
None
} else {
accepting_positions.iter().map(|p| p.num_errors).min()
}
}
pub fn min_distance(&self) -> Option<usize> {
self.state.min_distance()
}
pub fn infer_distance(&self, term_length: usize) -> Option<usize> {
self.state.infer_distance(term_length)
}
pub fn is_viable(&self) -> bool {
!self.state.is_empty()
}
pub fn query(&self) -> &[u8] {
&self.query
}
pub fn max_distance(&self) -> usize {
self.max_distance
}
pub fn algorithm(&self) -> Algorithm {
self.algorithm
}
pub fn state(&self) -> &State {
&self.state
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_creates_initial_state() {
let zipper = AutomatonZipper::new(b"test", 2, Algorithm::Standard);
assert!(zipper.is_viable());
assert_eq!(zipper.query(), b"test");
assert_eq!(zipper.max_distance(), 2);
assert_eq!(zipper.algorithm(), Algorithm::Standard);
}
#[test]
fn test_root_not_accepting() {
let zipper = AutomatonZipper::new(b"test", 1, Algorithm::Standard);
assert_eq!(zipper.min_distance_accepting(), None);
}
#[test]
fn test_exact_match_transition() {
let zipper = AutomatonZipper::new(b"cat", 1, Algorithm::Standard);
let mut pool = StatePool::new();
let z1 = zipper.transition(b'c', &mut pool).expect("c should work");
assert!(z1.is_viable());
assert_eq!(z1.min_distance_accepting(), None);
let z2 = z1.transition(b'a', &mut pool).expect("a should work");
assert!(z2.is_viable());
assert_eq!(z2.min_distance_accepting(), None);
let z3 = z2.transition(b't', &mut pool).expect("t should work");
assert!(z3.is_viable());
assert_eq!(z3.min_distance_accepting(), Some(0));
}
#[test]
fn test_single_substitution() {
let zipper = AutomatonZipper::new(b"cat", 1, Algorithm::Standard);
let mut pool = StatePool::new();
let z1 = zipper.transition(b'b', &mut pool).expect("b should work");
let z2 = z1.transition(b'a', &mut pool).expect("a should work");
let z3 = z2.transition(b't', &mut pool).expect("t should work");
assert_eq!(z3.min_distance_accepting(), Some(1));
}
#[test]
fn test_insertion() {
let zipper = AutomatonZipper::new(b"cat", 1, Algorithm::Standard);
let mut pool = StatePool::new();
let z1 = zipper.transition(b'c', &mut pool).expect("c should work");
let z2 = z1.transition(b'a', &mut pool).expect("a should work");
let z3 = z2.transition(b'r', &mut pool).expect("r should work");
let z4 = z3.transition(b't', &mut pool).expect("t should work");
assert_eq!(z4.min_distance_accepting(), Some(1));
}
#[test]
fn test_deletion() {
let zipper = AutomatonZipper::new(b"cart", 1, Algorithm::Standard);
let mut pool = StatePool::new();
let z1 = zipper.transition(b'c', &mut pool).expect("c should work");
let z2 = z1.transition(b'a', &mut pool).expect("a should work");
let z3 = z2.transition(b't', &mut pool).expect("t should work");
assert_eq!(z3.min_distance_accepting(), Some(1));
}
#[test]
fn test_exceeds_max_distance() {
let zipper = AutomatonZipper::new(b"cat", 1, Algorithm::Standard);
let mut pool = StatePool::new();
let z1_opt = zipper.transition(b'd', &mut pool);
if let Some(z1) = z1_opt {
let z2_opt = z1.transition(b'o', &mut pool);
if let Some(z2) = z2_opt {
let z3_opt = z2.transition(b'g', &mut pool);
if let Some(z3) = z3_opt {
if let Some(dist) = z3.min_distance_accepting() {
assert!(dist > 1, "Distance should exceed max_distance");
}
}
}
}
}
#[test]
fn test_infer_distance_short_term() {
let zipper = AutomatonZipper::new(b"test", 2, Algorithm::Standard);
let mut pool = StatePool::new();
let z1 = zipper
.transition(b't', &mut pool)
.expect("doc/test fixture: transition on valid dictionary path");
let z2 = z1
.transition(b'e', &mut pool)
.expect("test fixture: transition on valid dictionary path");
assert_eq!(z2.infer_distance(2), Some(0)); }
#[test]
fn test_clone_creates_independent_zipper() {
let zipper = AutomatonZipper::new(b"test", 1, Algorithm::Standard);
let mut pool = StatePool::new();
let clone1 = zipper.clone();
let clone2 = zipper.clone();
let _z1 = clone1.transition(b't', &mut pool);
assert_eq!(clone2.min_distance_accepting(), None);
}
#[test]
fn test_transposition_algorithm() {
let zipper = AutomatonZipper::new(b"ab", 1, Algorithm::Transposition);
let mut pool = StatePool::new();
let z1 = zipper.transition(b'b', &mut pool).expect("b should work");
let z2 = z1.transition(b'a', &mut pool).expect("a should work");
assert_eq!(z2.min_distance_accepting(), Some(1));
}
#[test]
fn test_empty_query() {
let zipper = AutomatonZipper::new(b"", 1, Algorithm::Standard);
let mut pool = StatePool::new();
assert_eq!(zipper.min_distance_accepting(), Some(0));
let z1 = zipper.transition(b'a', &mut pool).expect("should work");
assert_eq!(z1.min_distance_accepting(), Some(1));
}
}