use crate::word::Word;
struct Peq<W: Word> {
table: [W; 256],
m: u32,
}
impl<W: Word> Peq<W> {
fn new(pattern: &[u8]) -> Option<Self> {
let m = u32::try_from(pattern.len()).ok()?;
if m > W::BITS {
return None;
}
let mut table = [W::ZERO; 256];
for (i, &c) in pattern.iter().enumerate() {
#[allow(clippy::cast_possible_truncation)]
let bit = W::ONE.shl(i as u32);
table[usize::from(c)] = table[usize::from(c)].or(bit);
}
Some(Self { table, m })
}
}
#[derive(Clone, Copy)]
struct Column<W: Word> {
pv: W,
mv: W,
top: W,
}
impl<W: Word> Column<W> {
fn new(m: u32) -> Self {
Self {
pv: W::ONES,
mv: W::ZERO,
top: W::ONE.shl(m - 1),
}
}
#[inline]
fn step(&mut self, eq: W, charge_start: bool) -> i32 {
let xv = eq.or(self.mv);
let xh = eq.and(self.pv).wrapping_add(self.pv).xor(self.pv).or(eq);
let ph = self.mv.or(xh.or(self.pv).not());
let mh = self.pv.and(xh);
let delta = if !ph.and(self.top).is_zero() {
1
} else if !mh.and(self.top).is_zero() {
-1
} else {
0
};
let ph = if charge_start {
ph.shl(1).or(W::ONE)
} else {
ph.shl(1)
};
let mh = mh.shl(1);
self.pv = mh.or(xv.or(ph).not());
self.mv = ph.and(xv);
delta
}
}
#[must_use]
pub fn edit_distance<W: Word>(pattern: &[u8], text: &[u8]) -> Option<u32> {
let peq = Peq::<W>::new(pattern)?;
if peq.m == 0 {
return u32::try_from(text.len()).ok();
}
let mut col = Column::new(peq.m);
let mut score = i64::from(peq.m);
for &c in text {
score += i64::from(col.step(peq.table[usize::from(c)], true));
}
u32::try_from(score).ok()
}
#[must_use]
pub fn search<'t, W: Word>(pattern: &[u8], text: &'t [u8], max_dist: u32) -> Option<Search<'t, W>> {
let peq = Peq::<W>::new(pattern)?;
if peq.m == 0 {
return None;
}
let m = i64::from(peq.m);
let col = Column::new(peq.m);
Some(Search {
peq,
col,
score: m,
text,
pos: 0,
max_dist: i64::from(max_dist),
})
}
#[must_use]
pub fn min_distance<W: Word>(pattern: &[u8], text: &[u8]) -> Option<u32> {
let peq = Peq::<W>::new(pattern)?;
if peq.m == 0 {
return Some(0);
}
let mut col = Column::new(peq.m);
let mut score = i64::from(peq.m);
let mut best = score;
for &c in text {
score += i64::from(col.step(peq.table[usize::from(c)], false));
best = best.min(score);
}
u32::try_from(best).ok()
}
pub struct Search<'t, W: Word> {
peq: Peq<W>,
col: Column<W>,
score: i64,
text: &'t [u8],
pos: usize,
max_dist: i64,
}
impl<W: Word> Iterator for Search<'_, W> {
type Item = (usize, u32);
fn next(&mut self) -> Option<(usize, u32)> {
while let Some(&c) = self.text.get(self.pos) {
self.pos += 1;
self.score += i64::from(self.col.step(self.peq.table[usize::from(c)], false));
if self.score <= self.max_dist {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
return Some((self.pos, self.score as u32));
}
}
None
}
}
#[must_use]
pub fn distance(pattern: &[u8], text: &[u8]) -> Option<u32> {
match pattern.len() {
0..=64 => edit_distance::<u64>(pattern, text),
65..=128 => edit_distance::<u128>(pattern, text),
129..=256 => edit_distance::<crate::Wide<4>>(pattern, text),
257..=512 => edit_distance::<crate::Wide<8>>(pattern, text),
_ => None,
}
}