use crate::transducer::universal::{
CharacteristicVector, PositionVariant, UniversalPosition, UniversalState,
};
use crate::transducer::{SubstitutionPolicy, Unrestricted};
#[derive(Debug, Clone)]
pub struct UniversalAutomaton<V: PositionVariant, P: SubstitutionPolicy = Unrestricted> {
max_distance: u8,
_phantom: std::marker::PhantomData<(V, P)>,
}
impl<V: PositionVariant> UniversalAutomaton<V, Unrestricted> {
#[must_use]
pub fn new(max_distance: u8) -> Self {
Self {
max_distance,
_phantom: std::marker::PhantomData,
}
}
}
impl<V: PositionVariant, P: SubstitutionPolicy> UniversalAutomaton<V, P> {
#[must_use]
pub fn with_policy(max_distance: u8, _policy: P) -> Self {
Self {
max_distance,
_phantom: std::marker::PhantomData,
}
}
#[must_use]
pub fn max_distance(&self) -> u8 {
self.max_distance
}
fn initial_state(&self) -> UniversalState<V> {
let mut state = UniversalState::new(self.max_distance);
if let Ok(pos) = UniversalPosition::new_i(0, 0, self.max_distance) {
state.add_position(pos);
}
state
}
#[must_use]
fn is_accepting(&self, state: &UniversalState<V>, word_len: usize, input_len: usize) -> bool {
let n = self.max_distance as i32;
state.positions().any(|pos| {
if pos.is_m_type() {
pos.offset() <= 0 && pos.errors() <= self.max_distance
} else {
let current_word_pos = input_len as i32 + pos.offset();
if current_word_pos < 0 {
return false; }
let remaining_chars = word_len as i32 - current_word_pos;
let remaining_errors = n - (pos.errors() as i32);
remaining_chars >= 0 && remaining_chars <= remaining_errors
}
})
}
pub fn accepts(&self, word: &str, input: &str) -> bool {
if input.is_empty() {
return word.len() <= self.max_distance as usize;
}
if input.len() > word.len() + self.max_distance as usize {
return false;
}
let mut state = self.initial_state();
for (i, input_char) in input.chars().enumerate() {
let subword = self.relevant_subword(word, i + 1);
let bit_vector = CharacteristicVector::new(input_char, &subword);
if let Some(next_state) = state.transition(&bit_vector, i + 1) {
state = next_state;
} else {
return false;
}
}
self.is_accepting(&state, word.len(), input.len())
}
fn relevant_subword(&self, word: &str, position: usize) -> String {
let n = self.max_distance as i32;
let i = position as i32;
let start = i - n;
let v = std::cmp::min(word.len() as i32, i + n + 1);
let mut result = String::new();
for pos in start..=v {
if pos < 1 {
result.push('$');
} else if pos <= word.len() as i32 {
let idx = (pos - 1) as usize;
if let Some(ch) = word.chars().nth(idx) {
result.push(ch);
}
}
}
result
}
pub fn process(&self, bit_vectors: &[CharacteristicVector]) -> Option<UniversalState<V>> {
let mut state = self.initial_state();
for (i, bv) in bit_vectors.iter().enumerate() {
state = state.transition(bv, i + 1)?;
}
Some(state)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transducer::universal::Standard;
#[test]
fn test_new_automaton() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert_eq!(automaton.max_distance(), 2);
}
#[test]
fn test_initial_state() {
let automaton = UniversalAutomaton::<Standard>::new(2);
let state = automaton.initial_state();
let positions: Vec<_> = state.positions().collect();
assert_eq!(positions.len(), 1);
assert!(positions[0].is_i_type());
assert_eq!(positions[0].offset(), 0);
assert_eq!(positions[0].errors(), 0);
}
#[test]
fn test_is_accepting_i_type_at_word_end() {
let automaton = UniversalAutomaton::<Standard>::new(2);
let mut state = UniversalState::new(2);
state.add_position(
UniversalPosition::new_i(0, 0, 2)
.expect("test fixture: UniversalPosition::new_i with valid args"),
);
assert!(automaton.is_accepting(&state, 4, 4));
}
#[test]
fn test_is_accepting_i_type_before_word_end() {
let automaton = UniversalAutomaton::<Standard>::new(2);
let mut state = UniversalState::new(2);
state.add_position(
UniversalPosition::new_i(0, 0, 2)
.expect("test fixture: UniversalPosition::new_i with valid args"),
);
assert!(automaton.is_accepting(&state, 4, 2));
}
#[test]
fn test_is_accepting_m_type_state() {
let automaton = UniversalAutomaton::<Standard>::new(2);
let mut state = UniversalState::new(2);
state.add_position(
UniversalPosition::new_m(0, 0, 2)
.expect("test fixture: UniversalPosition::new_m with valid args"),
);
assert!(automaton.is_accepting(&state, 4, 5));
}
#[test]
fn test_is_accepting_mixed_state() {
let automaton = UniversalAutomaton::<Standard>::new(2);
let mut state = UniversalState::new(2);
state.add_position(
UniversalPosition::new_i(0, 0, 2)
.expect("test fixture: UniversalPosition::new_i with valid args"),
);
state.add_position(
UniversalPosition::new_m(-1, 1, 2)
.expect("test fixture: UniversalPosition::new_m with valid args"),
);
assert!(automaton.is_accepting(&state, 4, 5));
}
#[test]
fn test_not_accepting_too_many_remaining_chars() {
let automaton = UniversalAutomaton::<Standard>::new(2);
let mut state = UniversalState::new(2);
state.add_position(
UniversalPosition::new_i(0, 0, 2)
.expect("test fixture: UniversalPosition::new_i with valid args"),
);
assert!(!automaton.is_accepting(&state, 4, 0));
}
#[test]
fn test_relevant_subword_start() {
let automaton = UniversalAutomaton::<Standard>::new(2);
let subword = automaton.relevant_subword("test", 1);
assert_eq!(subword, "$$test");
}
#[test]
fn test_relevant_subword_middle() {
let automaton = UniversalAutomaton::<Standard>::new(2);
let subword = automaton.relevant_subword("test", 3);
assert_eq!(subword, "test");
}
#[test]
fn test_relevant_subword_end() {
let automaton = UniversalAutomaton::<Standard>::new(2);
let subword = automaton.relevant_subword("test", 4);
assert_eq!(subword, "est");
}
#[test]
fn test_relevant_subword_n1() {
let automaton = UniversalAutomaton::<Standard>::new(1);
let subword = automaton.relevant_subword("test", 2);
assert_eq!(subword, "test");
}
#[test]
fn test_accepts_identical() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert!(automaton.accepts("test", "test"));
}
#[test]
fn test_accepts_substitution() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert!(automaton.accepts("test", "text"));
}
#[test]
fn test_accepts_insertion() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert!(automaton.accepts("test", "teast"));
}
#[test]
fn test_accepts_deletion() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert!(automaton.accepts("test", "tet"));
}
#[test]
fn test_rejects_too_far() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert!(!automaton.accepts("test", "hello"));
}
#[test]
fn test_accepts_empty_to_empty() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert!(automaton.accepts("", ""));
}
#[test]
fn test_accepts_empty_word() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert!(automaton.accepts("", "ab"));
}
#[test]
fn test_rejects_empty_word_too_far() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert!(!automaton.accepts("", "abc"));
}
#[test]
fn test_accepts_to_empty() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert!(automaton.accepts("ab", ""));
}
#[test]
fn test_rejects_to_empty_too_far() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert!(!automaton.accepts("abc", ""));
}
#[test]
fn test_accepts_multiple_edits() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert!(automaton.accepts("test", "best"));
assert!(automaton.accepts("test", "tent"));
}
#[test]
fn test_accepts_n1() {
let automaton = UniversalAutomaton::<Standard>::new(1);
assert!(automaton.accepts("test", "text"));
assert!(automaton.accepts("test", "best"));
assert!(!automaton.accepts("test", "bear"));
}
#[test]
fn test_accepts_longer_words() {
let automaton = UniversalAutomaton::<Standard>::new(2);
assert!(automaton.accepts("algorithm", "algorythm"));
assert!(automaton.accepts("algorithm", "algarithm"));
}
#[test]
fn test_transposition_adjacent_swap_start() {
use crate::transducer::universal::Transposition;
let automaton = UniversalAutomaton::<Transposition>::new(1);
assert!(automaton.accepts("test", "etst"));
}
#[test]
fn test_transposition_adjacent_swap_middle() {
use crate::transducer::universal::Transposition;
let automaton = UniversalAutomaton::<Transposition>::new(1);
assert!(automaton.accepts("test", "tset"));
}
#[test]
fn test_transposition_adjacent_swap_end() {
use crate::transducer::universal::Transposition;
let automaton = UniversalAutomaton::<Transposition>::new(1);
assert!(automaton.accepts("test", "tets"));
}
#[test]
fn test_transposition_with_standard_operations() {
use crate::transducer::universal::Transposition;
let automaton = UniversalAutomaton::<Transposition>::new(2);
assert!(automaton.accepts("test", "set"));
assert!(automaton.accepts("test", "taset"));
}
#[test]
fn test_transposition_longer_words() {
use crate::transducer::universal::Transposition;
let automaton = UniversalAutomaton::<Transposition>::new(1);
assert!(automaton.accepts("algorithm", "lagorithm"));
assert!(automaton.accepts("algorithm", "aglorithm"));
}
#[test]
fn test_transposition_rejects_non_adjacent() {
use crate::transducer::universal::Transposition;
let automaton = UniversalAutomaton::<Transposition>::new(1);
assert!(!automaton.accepts("test", "stet"));
}
#[test]
fn test_transposition_empty_and_single_char() {
use crate::transducer::universal::Transposition;
let automaton = UniversalAutomaton::<Transposition>::new(1);
assert!(automaton.accepts("", ""));
assert!(automaton.accepts("a", "a"));
assert!(automaton.accepts("a", "b")); }
#[test]
fn test_transposition_two_chars() {
use crate::transducer::universal::Transposition;
let automaton = UniversalAutomaton::<Transposition>::new(1);
assert!(automaton.accepts("ab", "ba"));
assert!(automaton.accepts("xy", "yx"));
}
#[test]
fn test_transposition_distance_zero() {
use crate::transducer::universal::Transposition;
let automaton = UniversalAutomaton::<Transposition>::new(0);
assert!(automaton.accepts("test", "test"));
assert!(!automaton.accepts("test", "etst")); }
#[test]
fn test_transposition_vs_standard() {
use crate::transducer::universal::Transposition;
let trans_automaton = UniversalAutomaton::<Transposition>::new(1);
assert!(trans_automaton.accepts("test", "etst"));
let std_automaton = UniversalAutomaton::<Standard>::new(1);
assert!(!std_automaton.accepts("test", "etst"));
}
#[test]
fn test_transposition_multiple_swaps() {
use crate::transducer::universal::Transposition;
let automaton = UniversalAutomaton::<Transposition>::new(2);
assert!(automaton.accepts("abcd", "badc"));
}
#[test]
fn test_transposition_with_repeated_chars() {
use crate::transducer::universal::Transposition;
let automaton = UniversalAutomaton::<Transposition>::new(1);
assert!(automaton.accepts("abcd", "bacd"));
assert!(automaton.accepts("aabb", "abab"));
assert!(automaton.accepts("aabc", "aacb"));
}
#[test]
fn test_merge_and_split_distance_zero() {
use crate::transducer::universal::MergeAndSplit;
let automaton = UniversalAutomaton::<MergeAndSplit>::new(0);
assert!(automaton.accepts("", ""));
assert!(automaton.accepts("hello", "hello"));
assert!(!automaton.accepts("hello", "helo"));
assert!(!automaton.accepts("hello", "helllo"));
}
#[test]
fn test_merge_simple() {
use crate::transducer::universal::MergeAndSplit;
let automaton = UniversalAutomaton::<MergeAndSplit>::new(1);
assert!(automaton.accepts("ab", "a"));
assert!(automaton.accepts("abc", "ac")); assert!(automaton.accepts("xab", "xa")); assert!(automaton.accepts("xaby", "xay")); }
#[test]
fn test_split_simple() {
use crate::transducer::universal::MergeAndSplit;
let automaton = UniversalAutomaton::<MergeAndSplit>::new(1);
assert!(automaton.accepts("a", "ab"));
assert!(automaton.accepts("ac", "abc")); assert!(automaton.accepts("xa", "xab")); assert!(automaton.accepts("xay", "xaby"));
assert!(automaton.accepts("b", "bc")); assert!(automaton.accepts("t", "te")); }
#[test]
fn test_merge_and_split_longer_words() {
use crate::transducer::universal::MergeAndSplit;
let automaton = UniversalAutomaton::<MergeAndSplit>::new(1);
assert!(automaton.accepts("algorithm", "algorihm")); assert!(automaton.accepts("banana", "banna"));
assert!(automaton.accepts("algorithim", "algorithm")); assert!(automaton.accepts("banna", "banana")); }
#[test]
fn test_merge_and_split_with_standard_operations() {
use crate::transducer::universal::MergeAndSplit;
let automaton = UniversalAutomaton::<MergeAndSplit>::new(1);
assert!(automaton.accepts("test", "teest"));
assert!(automaton.accepts("test", "tst"));
assert!(automaton.accepts("test", "best"));
}
#[test]
fn test_merge_and_split_empty_and_single_char() {
use crate::transducer::universal::MergeAndSplit;
let automaton = UniversalAutomaton::<MergeAndSplit>::new(1);
assert!(automaton.accepts("", ""));
assert!(automaton.accepts("a", "a"));
assert!(automaton.accepts("a", "b")); assert!(automaton.accepts("a", "")); assert!(automaton.accepts("", "a"));
assert!(automaton.accepts("a", "ab"));
assert!(automaton.accepts("ab", "a"));
}
#[test]
fn test_merge_at_start() {
use crate::transducer::universal::MergeAndSplit;
let automaton = UniversalAutomaton::<MergeAndSplit>::new(1);
assert!(automaton.accepts("abcd", "acd")); assert!(automaton.accepts("test", "est")); }
#[test]
fn test_merge_at_end() {
use crate::transducer::universal::MergeAndSplit;
let automaton = UniversalAutomaton::<MergeAndSplit>::new(1);
assert!(automaton.accepts("test", "tes")); assert!(automaton.accepts("abcd", "abc")); }
#[test]
fn test_split_at_start() {
use crate::transducer::universal::MergeAndSplit;
let automaton = UniversalAutomaton::<MergeAndSplit>::new(1);
assert!(automaton.accepts("acd", "abcd")); assert!(automaton.accepts("est", "test")); }
#[test]
fn test_split_at_end() {
use crate::transducer::universal::MergeAndSplit;
let automaton = UniversalAutomaton::<MergeAndSplit>::new(1);
assert!(automaton.accepts("tes", "test")); assert!(automaton.accepts("abc", "abcd")); }
#[test]
fn test_merge_and_split_multiple_operations() {
use crate::transducer::universal::MergeAndSplit;
let automaton = UniversalAutomaton::<MergeAndSplit>::new(2);
assert!(automaton.accepts("abcd", "ac"));
assert!(automaton.accepts("ac", "abcd"));
assert!(automaton.accepts("abc", "abbc")); assert!(automaton.accepts("abbc", "abc")); }
#[test]
fn test_merge_and_split_vs_standard() {
use crate::transducer::universal::{MergeAndSplit, Standard};
let merge_split_automaton = UniversalAutomaton::<MergeAndSplit>::new(1);
let standard_automaton = UniversalAutomaton::<Standard>::new(1);
assert_eq!(
standard_automaton.accepts("test", "best"),
merge_split_automaton.accepts("test", "best")
);
assert_eq!(
standard_automaton.accepts("test", "tst"),
merge_split_automaton.accepts("test", "tst")
);
assert!(merge_split_automaton.accepts("ab", "a")); assert!(merge_split_automaton.accepts("a", "ab")); assert!(merge_split_automaton.accepts("abc", "ac")); assert!(merge_split_automaton.accepts("ac", "abc"));
}
#[test]
fn test_merge_and_split_with_repeated_chars() {
use crate::transducer::universal::MergeAndSplit;
let automaton = UniversalAutomaton::<MergeAndSplit>::new(1);
assert!(automaton.accepts("aab", "ab")); assert!(automaton.accepts("aabb", "abb")); assert!(automaton.accepts("abbb", "abb"));
assert!(automaton.accepts("ab", "aab")); assert!(automaton.accepts("abb", "aabb")); assert!(automaton.accepts("abb", "abbb")); }
}