use super::{Algorithm, Position, State, StatePool, SubstitutionPolicy, SubstitutionPolicyFor};
use libdictenstein::CharUnit;
use smallvec::SmallVec;
#[inline]
fn characteristic_vector<'a, U: CharUnit, P: SubstitutionPolicy + SubstitutionPolicyFor<U>>(
policy: P,
dict_unit: U,
query: &[U],
window_size: usize,
offset: usize,
buffer: &'a mut [bool; 8],
) -> &'a [bool] {
let len = window_size.min(8);
for (i, item) in buffer.iter_mut().enumerate().take(len) {
let query_idx = offset + i;
if query_idx < query.len() {
let query_unit = query[query_idx];
*item =
query_unit == dict_unit || is_substitution_allowed(&policy, dict_unit, query_unit);
} else {
*item = false;
}
}
&buffer[..len]
}
#[inline(always)]
fn is_substitution_allowed<U: CharUnit, P: SubstitutionPolicy + SubstitutionPolicyFor<U>>(
policy: &P,
dict_unit: U,
query_unit: U,
) -> bool {
policy.is_allowed_for(dict_unit, query_unit)
}
#[inline]
pub fn transition_position(
position: &Position,
characteristic_vector: &[bool],
query_length: usize,
max_distance: usize,
algorithm: Algorithm,
prefix_mode: bool,
) -> SmallVec<[Position; 4]> {
match algorithm {
Algorithm::Standard => transition_standard(
position,
characteristic_vector,
query_length,
max_distance,
prefix_mode,
),
Algorithm::Transposition => transition_transposition(
position,
characteristic_vector,
query_length,
max_distance,
prefix_mode,
),
Algorithm::MergeAndSplit => transition_merge_split(
position,
characteristic_vector,
query_length,
max_distance,
prefix_mode,
),
}
}
#[inline]
fn index_of_match(cv: &[bool], start: usize, limit: usize) -> Option<usize> {
(0..limit).find(|&j| cv.get(start + j).copied().unwrap_or(false))
}
#[inline]
fn transition_standard(
position: &Position,
cv: &[bool],
query_length: usize,
max_distance: usize,
prefix_mode: bool,
) -> SmallVec<[Position; 4]> {
let mut next = SmallVec::new();
let i = position.term_index;
let e = position.num_errors;
let h = 0; let w = cv.len();
if prefix_mode && i >= query_length {
next.push(Position::new(i, e));
return next;
}
if e < max_distance {
if h + 2 <= w {
let a = max_distance.saturating_sub(e).saturating_add(1);
let b = w - h;
let k = a.min(b);
match index_of_match(cv, h, k) {
Some(0) => {
next.push(Position::new(i + 1, e));
}
Some(j) => {
next.push(Position::new(i, e + 1)); next.push(Position::new(i + 1, e + 1)); next.push(Position::new(i + j + 1, e + j)); }
None => {
next.push(Position::new(i, e + 1)); next.push(Position::new(i + 1, e + 1)); }
}
}
else if h + 1 == w {
if cv[h] {
next.push(Position::new(i + 1, e));
} else {
next.push(Position::new(i, e + 1)); next.push(Position::new(i + 1, e + 1)); }
}
else {
next.push(Position::new(i, e + 1));
}
}
else if e == max_distance && h < w && cv[h] {
next.push(Position::new(i + 1, max_distance));
}
next
}
#[inline]
fn transition_transposition(
position: &Position,
cv: &[bool],
query_length: usize,
max_distance: usize,
prefix_mode: bool,
) -> SmallVec<[Position; 4]> {
let mut next = SmallVec::new();
let i = position.term_index;
let e = position.num_errors;
let t = position.is_special; let h = 0; let w = cv.len();
if prefix_mode && i >= query_length {
next.push(Position::new(i, e));
return next;
}
if e == 0 && max_distance > 0 {
if h + 2 <= w {
let a = max_distance.saturating_add(1);
let b = w - h;
let k = a.min(b);
match index_of_match(cv, h, k) {
Some(0) => {
next.push(Position::new(i + 1, 0));
}
Some(1) => {
next.push(Position::new(i, 1)); next.push(Position::new_special(i, 1)); next.push(Position::new(i + 1, 1)); next.push(Position::new(i + 2, 1)); }
Some(j) => {
next.push(Position::new(i, 1)); next.push(Position::new(i + 1, 1)); next.push(Position::new(i + j + 1, j)); }
None => {
next.push(Position::new(i, 1)); next.push(Position::new(i + 1, 1)); }
}
} else if h + 1 == w {
if cv[h] {
next.push(Position::new(i + 1, 0));
} else {
next.push(Position::new(i, 1));
next.push(Position::new(i + 1, 1));
}
} else {
next.push(Position::new(i, 1));
}
}
else if e >= 1 && e < max_distance {
if h + 2 <= w {
if !t {
let a = max_distance.saturating_sub(e).saturating_add(1);
let b = w - h;
let k = a.min(b);
match index_of_match(cv, h, k) {
Some(0) => {
next.push(Position::new(i + 1, e));
}
Some(1) => {
next.push(Position::new(i, e + 1));
next.push(Position::new_special(i, e + 1));
next.push(Position::new(i + 1, e + 1));
next.push(Position::new(i + 2, e + 1));
}
Some(j) => {
next.push(Position::new(i, e + 1));
next.push(Position::new(i + 1, e + 1));
next.push(Position::new(i + j + 1, e + j));
}
None => {
next.push(Position::new(i, e + 1));
next.push(Position::new(i + 1, e + 1));
}
}
} else {
if cv[h] {
next.push(Position::new(i + 2, e));
}
}
} else if h + 1 == w {
if cv[h] {
next.push(Position::new(i + 1, e));
} else {
next.push(Position::new(i, e + 1));
next.push(Position::new(i + 1, e + 1));
}
} else {
next.push(Position::new(i, e + 1));
}
}
else if e == max_distance {
if h < w && !t {
if cv[h] {
next.push(Position::new(i + 1, max_distance));
}
} else if h + 2 <= w && t && cv[h] {
next.push(Position::new(i + 2, max_distance));
}
}
next
}
#[inline]
fn transition_merge_split(
position: &Position,
cv: &[bool],
query_length: usize,
max_distance: usize,
prefix_mode: bool,
) -> SmallVec<[Position; 4]> {
let mut next = SmallVec::new();
let i = position.term_index;
let e = position.num_errors;
let s = position.is_special; let h = 0; let w = cv.len();
if prefix_mode && i >= query_length {
next.push(Position::new(i, e));
return next;
}
if e == 0 && max_distance > 0 {
if h + 2 <= w {
if cv[h] {
next.push(Position::new(i + 1, e));
} else {
next.push(Position::new(i, e + 1)); if i < query_length {
next.push(Position::new_special(i, e + 1)); }
next.push(Position::new(i + 1, e + 1)); if i + 2 <= query_length {
next.push(Position::new(i + 2, e + 1));
}
}
} else if h + 1 == w {
if cv[h] {
next.push(Position::new(i + 1, e));
} else {
next.push(Position::new(i, e + 1));
if i < query_length {
next.push(Position::new_special(i, e + 1));
}
next.push(Position::new(i + 1, e + 1));
}
} else {
next.push(Position::new(i, e + 1));
}
}
else if e < max_distance {
if h + 2 <= w {
if !s {
if cv[h] {
next.push(Position::new(i + 1, e));
} else {
next.push(Position::new(i, e + 1));
if i < query_length {
next.push(Position::new_special(i, e + 1));
}
next.push(Position::new(i + 1, e + 1));
if i + 2 <= query_length {
next.push(Position::new(i + 2, e + 1));
}
}
} else {
next.push(Position::new(i + 1, e));
}
} else if h + 1 == w {
if !s {
if cv[h] {
next.push(Position::new(i + 1, e));
} else {
next.push(Position::new(i, e + 1));
if i < query_length {
next.push(Position::new_special(i, e + 1));
}
next.push(Position::new(i + 1, e + 1));
}
} else {
next.push(Position::new(i + 1, e));
}
} else {
next.push(Position::new(i, e + 1));
}
}
else if e == max_distance && h < w {
if !s {
if cv[h] {
next.push(Position::new(i + 1, max_distance));
}
} else {
next.push(Position::new(i + 1, e));
}
}
next
}
#[inline]
fn epsilon_closure_mut(
state: &mut State,
query_length: usize,
max_distance: usize,
algorithm: Algorithm,
) {
let mut to_process: SmallVec<[Position; 8]> = SmallVec::with_capacity(8);
for pos in state.positions() {
to_process.push(*pos);
}
let mut processed = 0;
while processed < to_process.len() {
let position = &to_process[processed];
processed += 1;
if position.num_errors < max_distance && position.term_index < query_length {
let deleted = Position::new(position.term_index + 1, position.num_errors + 1);
let len_before = state.len();
state.insert(deleted, algorithm, query_length);
if state.len() > len_before {
to_process.push(deleted);
}
}
}
}
fn epsilon_closure(
state: &State,
query_length: usize,
max_distance: usize,
algorithm: Algorithm,
) -> State {
let mut result = state.clone();
epsilon_closure_mut(&mut result, query_length, max_distance, algorithm);
result
}
#[inline]
fn epsilon_closure_into(
source: &State,
target: &mut State,
query_length: usize,
max_distance: usize,
algorithm: Algorithm,
) {
target.copy_from(source);
epsilon_closure_mut(target, query_length, max_distance, algorithm);
}
pub fn transition_state<U: CharUnit, P: SubstitutionPolicy + SubstitutionPolicyFor<U>>(
state: &State,
policy: P,
dict_unit: U,
query: &[U],
max_distance: usize,
algorithm: Algorithm,
prefix_mode: bool,
) -> Option<State> {
let window_size = max_distance.saturating_add(1);
let query_length = query.len();
let expanded_state = epsilon_closure(state, query_length, max_distance, algorithm);
let mut next_state = State::new();
let mut cv_buffer = [false; 8];
for position in expanded_state.positions() {
let offset = position.term_index;
let cv = characteristic_vector(
policy,
dict_unit,
query,
window_size,
offset,
&mut cv_buffer,
);
let next_positions = transition_position(
position,
cv,
query_length,
max_distance,
algorithm,
prefix_mode,
);
for next_pos in next_positions {
next_state.insert(next_pos, algorithm, query_length);
}
}
if next_state.is_empty() {
None
} else {
Some(next_state)
}
}
#[inline]
pub fn transition_state_pooled<U: CharUnit, P: SubstitutionPolicy + SubstitutionPolicyFor<U>>(
state: &State,
pool: &mut StatePool,
policy: P,
dict_unit: U,
query: &[U],
max_distance: usize,
algorithm: Algorithm,
prefix_mode: bool,
) -> Option<State> {
let window_size = max_distance.saturating_add(1);
let query_length = query.len();
let mut expanded_state = pool.acquire();
epsilon_closure_into(
state,
&mut expanded_state,
query_length,
max_distance,
algorithm,
);
let mut next_state = pool.acquire();
let mut cv_buffer = [false; 8];
for position in expanded_state.positions() {
let offset = position.term_index;
let cv = characteristic_vector(
policy,
dict_unit,
query,
window_size,
offset,
&mut cv_buffer,
);
let next_positions = transition_position(
position,
cv,
query_length,
max_distance,
algorithm,
prefix_mode,
);
for next_pos in next_positions {
next_state.insert(next_pos, algorithm, query_length);
}
}
pool.release(expanded_state);
if next_state.is_empty() {
pool.release(next_state);
None
} else {
Some(next_state)
}
}
pub fn initial_state(query_length: usize, max_distance: usize, algorithm: Algorithm) -> State {
let mut state = State::new();
state.insert(Position::new(0, 0), algorithm, query_length);
for i in 1..=max_distance.min(query_length) {
state.insert(Position::new(i, i), algorithm, query_length);
}
state
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transducer::Unrestricted;
#[test]
fn test_characteristic_vector() {
let query = b"test";
let mut buffer = [false; 8];
let policy = Unrestricted;
let cv = characteristic_vector(policy, b't', query, 3, 0, &mut buffer);
assert_eq!(cv, &[true, false, false]);
let cv = characteristic_vector(policy, b'e', query, 3, 0, &mut buffer);
assert_eq!(cv, &[false, true, false]);
let cv = characteristic_vector(policy, b's', query, 3, 1, &mut buffer);
assert_eq!(cv, &[false, true, false]);
}
#[test]
fn test_transition_standard_match() {
let pos = Position::new(0, 0);
let cv = vec![true, false, false]; let query_length = 4; let next = transition_standard(&pos, &cv, query_length, 2, false);
assert!(next.contains(&Position::new(1, 0)));
}
#[test]
fn test_transition_standard_operations() {
let pos = Position::new(1, 0);
let cv = vec![false, false, true]; let query_length = 4; let next = transition_standard(&pos, &cv, query_length, 2, false);
assert!(next.len() >= 2);
assert!(next.contains(&Position::new(1, 1))); }
#[test]
fn test_initial_state() {
let state = initial_state(5, 2, Algorithm::Standard);
assert_eq!(state.len(), 1);
assert!(state.positions().contains(&Position::new(0, 0)));
}
#[test]
fn test_transition_state() {
let query = b"test";
let max_distance = 2;
let mut state = State::new();
state.insert(Position::new(0, 0), Algorithm::Standard, max_distance);
let policy = Unrestricted;
let next = transition_state(
&state,
policy,
b't',
query,
max_distance,
Algorithm::Standard,
false,
);
assert!(next.is_some());
let next_state = next.expect("test fixture: transition produces Some (asserted above)");
assert!(next_state.positions().iter().any(|p| p.term_index > 0));
}
}