use std::cmp::Ordering;
use crate::Sink;
#[derive(Clone, Copy, PartialEq, Eq)]
struct Score {
matches: u32,
shift: u64,
blocks: u32,
drift: u64,
}
impl Score {
const ZERO: Score = Score {
matches: 0,
shift: 0,
blocks: 0,
drift: 0,
};
const NONE: Score = Score {
matches: 0,
shift: u64::MAX,
blocks: u32::MAX,
drift: u64::MAX,
};
fn better_than(self, other: Score) -> bool {
self.cmp_quality(other) == Ordering::Greater
}
fn cmp_quality(self, other: Score) -> Ordering {
self.matches
.cmp(&other.matches)
.then(other.shift.cmp(&self.shift))
.then(other.blocks.cmp(&self.blocks))
.then(other.drift.cmp(&self.drift))
}
}
const GAP_FROM_DEL_GAP: u8 = 0;
const GAP_FROM_DEL_MATCH: u8 = 1;
const GAP_FROM_INS_GAP: u8 = 2;
const GAP_FROM_INS_MATCH: u8 = 3;
const MATCH_CONTINUES: u8 = 1 << 2;
pub(crate) fn diff(a: &[u32], b: &[u32], ao: usize, bo: usize, sink: &mut Sink) {
let (n, m) = (a.len(), b.len());
let w = m + 1;
let mut back = vec![0u8; (n + 1) * w];
let mut prev_gap = vec![Score::NONE; w];
let mut prev_match = vec![Score::NONE; w];
let mut cur_gap = vec![Score::NONE; w];
let mut cur_match = vec![Score::NONE; w];
let shift = |i: usize, j: usize| (ao + i).abs_diff(bo + j) as u64;
for i in 0..=n {
for j in 0..=m {
let cell = i * w + j;
let (mut best, mut how) = if i == 0 && j == 0 {
(Score::ZERO, GAP_FROM_DEL_GAP)
} else {
(Score::NONE, GAP_FROM_DEL_GAP)
};
let mut consider = |s: Score, h: u8| {
if s != Score::NONE && (best == Score::NONE || s.better_than(best)) {
best = s;
how = h;
}
};
if i > 0 {
consider(prev_gap[j], GAP_FROM_DEL_GAP);
consider(prev_match[j], GAP_FROM_DEL_MATCH);
}
if j > 0 {
consider(cur_gap[j - 1], GAP_FROM_INS_GAP);
consider(cur_match[j - 1], GAP_FROM_INS_MATCH);
}
cur_gap[j] = best;
let mut bits = how;
cur_match[j] = Score::NONE;
if i > 0 && j > 0 && a[i - 1] == b[j - 1] {
let cont = prev_match[j - 1];
let start = prev_gap[j - 1];
let d = shift(i - 1, j - 1);
let cont = (cont != Score::NONE).then(|| Score {
matches: cont.matches + 1,
drift: cont.drift + d,
..cont
});
let start = (start != Score::NONE).then(|| Score {
matches: start.matches + 1,
shift: start.shift + d,
blocks: start.blocks + 1,
drift: start.drift + d,
});
cur_match[j] = match (cont, start) {
(Some(c), Some(s)) if !s.better_than(c) => {
bits |= MATCH_CONTINUES;
c
}
(Some(c), None) => {
bits |= MATCH_CONTINUES;
c
}
(_, Some(s)) => s,
(None, None) => Score::NONE,
};
}
back[cell] = bits;
}
std::mem::swap(&mut prev_gap, &mut cur_gap);
std::mem::swap(&mut prev_match, &mut cur_match);
}
let mut in_match = prev_match[m] != Score::NONE && prev_match[m].better_than(prev_gap[m]);
let mut rev = Vec::with_capacity(n + m);
let (mut i, mut j) = (n, m);
while i > 0 || j > 0 {
let bits = back[i * w + j];
if in_match {
rev.push((Some(i - 1), Some(j - 1)));
in_match = bits & MATCH_CONTINUES != 0;
i -= 1;
j -= 1;
} else {
match bits & 0b11 {
GAP_FROM_DEL_GAP | GAP_FROM_DEL_MATCH => {
rev.push((Some(i - 1), None));
in_match = bits & 0b11 == GAP_FROM_DEL_MATCH;
i -= 1;
}
_ => {
rev.push((None, Some(j - 1)));
in_match = bits & 0b11 == GAP_FROM_INS_MATCH;
j -= 1;
}
}
}
}
for step in rev.into_iter().rev() {
match step {
(Some(x), Some(y)) => sink.equal(ao + x, bo + y),
(Some(x), None) => sink.delete(ao + x),
(None, Some(y)) => sink.insert(bo + y),
(None, None) => unreachable!(),
}
}
}