use crate::{GBZ, support};
use std::collections::BTreeMap;
use std::fmt::{Display, Formatter};
use std::{cmp, fmt};
#[cfg(test)]
mod tests;
pub fn naive_weighted_lcs<F: Fn(usize) -> usize>(a: &[usize], b: &[usize], weight: F) -> (Vec<(usize, usize)>, usize) {
let mut dp = vec![vec![0; b.len() + 1]; a.len() + 1];
for (i, a_val) in a.iter().enumerate() {
for (j, b_val) in b.iter().enumerate() {
dp[i + 1][j + 1] = cmp::max(dp[i + 1][j], dp[i][j + 1]);
if a_val == b_val {
dp[i + 1][j + 1] = cmp::max(dp[i + 1][j + 1], dp[i][j] + weight(a[i]));
}
}
}
let mut result = Vec::new();
let mut i = a.len();
let mut j = b.len();
while i > 0 && j > 0 {
if a[i - 1] == b[j - 1] {
result.push((i - 1, j - 1));
i -= 1;
j -= 1;
} else if dp[i][j] == dp[i - 1][j] {
i -= 1;
} else {
j -= 1;
}
}
result.reverse();
(result, dp[a.len()][b.len()])
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct DPPoint {
weight: usize,
a_offset: usize,
b_offset: usize,
matches: usize,
}
impl DPPoint {
fn new(weight: usize, a_offset: usize, b_offset: usize) -> DPPoint {
DPPoint {
weight, a_offset, b_offset, matches: 0,
}
}
}
impl Display for DPPoint {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "(weight {}, a {}, b {}, matches {})", self.weight, self.a_offset, self.b_offset, self.matches)
}
}
struct DPMatrix<'a> {
a: &'a [usize],
b: &'a [usize],
a_weights: Vec<usize>,
b_weights: Vec<usize>,
points: BTreeMap<(usize, isize), DPPoint>,
}
impl<'a> DPMatrix<'a> {
fn prefix_sum<F: Fn(usize) -> usize>(sequence: &[usize], weight: F) -> Vec<usize> {
let mut result = Vec::with_capacity(sequence.len() + 1);
result.push(0);
for i in 0..sequence.len() {
result.push(result[i] + weight(sequence[i]));
}
result
}
fn new<F: Fn(usize) -> usize>(a: &'a [usize], b: &'a [usize], weight: F) -> Self {
let a_weights = Self::prefix_sum(a, &weight);
let b_weights = Self::prefix_sum(b, &weight);
let mut points = BTreeMap::new();
points.insert((0, 0), DPPoint::new(0, 0, 0));
DPMatrix {
a, b, a_weights, b_weights, points
}
}
fn diagonals_for(&self, edits: usize) -> Vec<isize> {
let mut result = Vec::new();
for ((_, diagonal), _) in self.points.range((edits, isize::MIN)..(edits + 1, isize::MIN)) {
result.push(*diagonal);
}
result
}
fn try_insert(&mut self, edits: usize, diagonal: isize, point: DPPoint) {
if let Some(existing) = self.points.get_mut(&(edits, diagonal)) {
if point.weight > existing.weight {
*existing = point;
}
} else {
self.points.insert((edits, diagonal), point);
}
}
fn a_weight(&self, offset: usize) -> usize {
self.a_weights[offset + 1] - self.a_weights[offset]
}
fn b_weight(&self, offset: usize) -> usize {
self.b_weights[offset + 1] - self.b_weights[offset]
}
fn extend(&mut self, edits: usize) -> Option<DPPoint> {
let diagonals = self.diagonals_for(edits);
for diagonal in diagonals.into_iter() {
let point = self.points.get(&(edits, diagonal)).copied();
if point.is_none() {
continue;
}
let mut point = point.unwrap();
while point.a_offset < self.a.len() && point.b_offset < self.b.len() && self.a[point.a_offset] == self.b[point.b_offset] {
point.weight += 2 * self.a_weight(point.a_offset);
point.a_offset += 1;
point.b_offset += 1;
point.matches += 1;
}
if point.matches > 0 {
self.points.insert((edits, diagonal), point);
}
if point.a_offset == self.a.len() && point.b_offset == self.b.len() {
return Some(point);
}
if point.a_offset < self.a.len() {
let weight = self.a_weight(point.a_offset);
let new_point = DPPoint::new(point.weight, point.a_offset + 1, point.b_offset);
self.try_insert(edits + weight, diagonal + (weight as isize), new_point);
}
if point.b_offset < self.b.len() {
let weight = self.b_weight(point.b_offset);
let new_point = DPPoint::new(point.weight, point.a_offset, point.b_offset + 1);
self.try_insert(edits + weight, diagonal - (weight as isize), new_point);
}
}
None
}
fn next_edits(&self, edits: usize) -> Option<usize> {
if let Some(((value, _), _)) = self.points.range((edits + 1, isize::MIN)..).next() {
Some(*value)
} else {
None
}
}
fn predecessor(&self, a_offset: usize, b_offset: usize, edits: usize) -> Option<(DPPoint, usize)> {
let diagonal = (self.a_weights[a_offset] as isize) - (self.b_weights[b_offset] as isize);
let prev = if a_offset > 0 && self.a_weight(a_offset - 1) <= edits {
let weight = self.a_weight(a_offset - 1);
self.points.get(&(edits - weight, diagonal - (weight as isize))).copied()
} else {
None
};
let next = if b_offset > 0 && self.b_weight(b_offset - 1) <= edits {
let weight = self.b_weight(b_offset - 1);
self.points.get(&(edits - weight, diagonal + (weight as isize))).copied()
} else {
None
};
if prev.is_some() && next.is_some() {
let prev = prev.unwrap();
let next = next.unwrap();
if prev.weight > next.weight {
Some((prev, edits - self.a_weight(a_offset - 1)))
} else {
Some((next, edits - self.b_weight(b_offset - 1)))
}
} else if let Some(point) = prev {
Some((point, edits - self.a_weight(a_offset - 1)))
} else if let Some(point) = next {
Some((point, edits - self.b_weight(b_offset - 1)))
} else {
None
}
}
}
pub fn fast_weighted_lcs<F: Fn(usize) -> usize>(a: &[usize], b: &[usize], weight: F) -> (Vec<(usize, usize)>, usize) {
if a.is_empty() || b.is_empty() {
return (Vec::new(), 0);
}
let mut matrix = DPMatrix::new(a, b, &weight);
let mut edits = 0;
let mut point = DPPoint::new(0, 0, 0);
loop {
if let Some(next_point) = matrix.extend(edits) {
point = next_point;
break;
}
if let Some(next_edits) = matrix.next_edits(edits) {
edits = next_edits;
} else {
break;
}
}
let mut result = Vec::new();
let final_weight = point.weight / 2;
loop {
for _ in 0..point.matches {
point.a_offset -= 1;
point.b_offset -= 1;
result.push((point.a_offset, point.b_offset));
}
if let Some((p, e)) = matrix.predecessor(point.a_offset, point.b_offset, edits) {
point = p;
edits = e;
} else {
break;
}
}
result.reverse();
(result, final_weight)
}
pub fn lcs(a: &[usize], b: &[usize]) -> Vec<(usize, usize)> {
fast_weighted_lcs(a, b, |_| 1).0
}
pub fn path_lcs(a: &[usize], b: &[usize], graph: &GBZ) -> (Vec<(usize, usize)>, usize) {
let weight = |handle| graph.sequence_len(support::node_id(handle)).unwrap_or(0);
fast_weighted_lcs(a, b, weight)
}