use super::algorithm::Algorithm;
use super::position::Position;
use smallvec::SmallVec;
use std::collections::BTreeSet;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct State {
positions: SmallVec<[Position; 8]>,
}
impl State {
pub fn new() -> Self {
Self {
positions: SmallVec::new(),
}
}
pub fn single(position: Position) -> Self {
let mut positions = SmallVec::new();
positions.push(position);
Self { positions }
}
pub fn from_positions(mut positions: Vec<Position>) -> Self {
positions.sort();
positions.dedup();
Self {
positions: SmallVec::from_vec(positions),
}
}
pub fn insert(&mut self, position: Position, algorithm: Algorithm, query_length: usize) {
for existing in &self.positions {
if existing.subsumes(&position, algorithm, query_length) {
return; }
}
self.positions
.retain(|p| !position.subsumes(p, algorithm, query_length));
let insert_pos = self
.positions
.binary_search(&position)
.unwrap_or_else(|pos| pos);
self.positions.insert(insert_pos, position);
}
pub fn merge(&mut self, other: &State, algorithm: Algorithm, query_length: usize) {
for position in &other.positions {
self.insert(*position, algorithm, query_length);
}
}
pub fn head(&self) -> Option<&Position> {
self.positions.first()
}
#[inline(always)]
pub fn positions(&self) -> &[Position] {
&self.positions
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.positions.is_empty()
}
#[inline(always)]
pub fn len(&self) -> usize {
self.positions.len()
}
pub fn iter(&self) -> impl Iterator<Item = &Position> {
self.positions.iter()
}
#[inline]
pub fn clear(&mut self) {
self.positions.clear();
}
#[inline]
pub fn copy_from(&mut self, other: &State) {
self.positions.clear();
self.positions.reserve(other.positions.len());
for pos in &other.positions {
self.positions.push(*pos); }
}
#[inline]
pub fn min_distance(&self) -> Option<usize> {
self.positions.first().map(|first| {
if self.positions.len() == 1 {
return first.num_errors;
}
#[cfg(target_arch = "x86_64")]
{
let len = self.positions.len();
if (4..=8).contains(&len) {
let errors: smallvec::SmallVec<[usize; 8]> =
self.positions.iter().map(|p| p.num_errors).collect();
return super::simd::find_minimum_simd(&errors, len);
}
}
self.positions
.iter()
.map(|p| p.num_errors)
.min()
.expect("State::min_distance: positions non-empty (first exists)")
})
}
#[inline]
pub fn infer_distance(&self, query_length: usize) -> Option<usize> {
if self.positions.len() == 1 {
let p = &self.positions[0];
if p.is_special {
return None;
}
let remaining = query_length.saturating_sub(p.term_index);
return Some(p.num_errors + remaining);
}
self.positions
.iter()
.filter(|p| !p.is_special) .map(|p| {
let remaining = query_length.saturating_sub(p.term_index);
p.num_errors + remaining
})
.min()
}
#[inline]
pub fn infer_prefix_distance(&self, query_length: usize) -> Option<usize> {
if self.positions.len() == 1 {
let p = &self.positions[0];
return if p.term_index >= query_length {
Some(p.num_errors)
} else {
None
};
}
self.positions
.iter()
.filter(|p| p.term_index >= query_length)
.map(|p| p.num_errors)
.min()
}
}
impl Default for State {
fn default() -> Self {
Self::new()
}
}
impl FromIterator<Position> for State {
fn from_iter<T: IntoIterator<Item = Position>>(iter: T) -> Self {
let positions: BTreeSet<Position> = iter.into_iter().collect();
Self::from_positions(positions.into_iter().collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_state_creation() {
let state = State::new();
assert!(state.is_empty());
assert_eq!(state.len(), 0);
}
#[test]
fn test_state_single_position() {
let pos = Position::new(3, 1);
let state = State::single(pos);
assert_eq!(state.len(), 1);
assert_eq!(state.head(), Some(&pos));
}
#[test]
fn test_state_insert_maintains_order() {
let mut state = State::new();
let max_distance = 3;
state.insert(Position::new(2, 2), Algorithm::Standard, max_distance);
state.insert(Position::new(3, 1), Algorithm::Standard, max_distance); state.insert(Position::new(4, 2), Algorithm::Standard, max_distance);
let positions: Vec<_> = state.positions().to_vec();
assert_eq!(positions.len(), 1);
assert_eq!(positions[0], Position::new(3, 1));
}
#[test]
fn test_state_subsumption() {
let mut state = State::new();
let max_distance = 3;
state.insert(Position::new(5, 2), Algorithm::Standard, max_distance);
assert_eq!(state.len(), 1);
state.insert(Position::new(4, 3), Algorithm::Standard, max_distance); assert_eq!(state.len(), 1, "(4,3) should be subsumed by (5,2)");
state.insert(Position::new(5, 1), Algorithm::Standard, max_distance); assert_eq!(state.len(), 1, "(5,1) should replace (5,2)");
let pos_at_5 = state
.positions()
.iter()
.find(|p| p.term_index == 5)
.expect("test fixture: position with term_index 5 was just inserted");
assert_eq!(pos_at_5.num_errors, 1);
}
#[test]
fn test_state_min_distance() {
let mut state = State::new();
let max_distance = 3;
state.insert(Position::new(3, 2), Algorithm::Standard, max_distance);
state.insert(Position::new(4, 1), Algorithm::Standard, max_distance);
state.insert(Position::new(5, 3), Algorithm::Standard, max_distance);
assert_eq!(state.min_distance(), Some(1));
}
#[test]
fn test_state_infer_distance() {
let mut state = State::new();
let max_distance = 3;
state.insert(Position::new(3, 1), Algorithm::Standard, max_distance); state.insert(Position::new(4, 2), Algorithm::Standard, max_distance);
let query_length = 7;
assert_eq!(state.infer_distance(query_length), Some(5));
}
}