use serde::{Deserialize, Serialize};
use crate::{number::UInt, strings::Penalties};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
Diagonal,
Up,
Left,
}
#[derive(Clone, Debug, PartialEq, Eq, Copy, Serialize, Deserialize)]
pub enum Edit {
Del(usize),
Ins(usize, char),
Sub(usize, char),
}
pub fn compute_table<U: UInt>(x: &str, y: &str, penalties: Penalties<U>) -> Vec<Vec<(U, Direction)>> {
let mut table = vec![vec![(U::ZERO, Direction::Diagonal); x.len() + 1]; y.len() + 1];
table[0][0] = (U::ZERO, Direction::Diagonal);
for (i, row) in table.iter_mut().enumerate().skip(1) {
row[0] = (penalties.gap * U::from(i), Direction::Up);
}
for (j, cell) in table[0].iter_mut().enumerate().skip(1) {
*cell = (penalties.gap * U::from(j), Direction::Left);
}
for (i, y_c) in y.chars().enumerate() {
for (j, x_c) in x.chars().enumerate() {
let mismatch_penalty = if x_c == y_c {
penalties.match_
} else {
penalties.mismatch
};
let d00 = (table[i][j].0 + mismatch_penalty, Direction::Diagonal);
let d01 = (table[i][j + 1].0 + penalties.gap, Direction::Up);
let d10 = (table[i + 1][j].0 + penalties.gap, Direction::Left);
table[i + 1][j + 1] = min2(d00, min2(d01, d10));
}
}
table
}
fn min2<U: UInt>(a: (U, Direction), b: (U, Direction)) -> (U, Direction) {
if a.0 <= b.0 {
a
} else {
b
}
}
#[must_use]
pub fn trace_back_iterative<U: UInt>(table: &[Vec<(U, Direction)>], [x, y]: [&str; 2]) -> (String, String) {
let (x, y) = (x.as_bytes(), y.as_bytes());
let (mut row_i, mut col_i) = (y.len(), x.len());
let (mut aligned_x, mut aligned_y) = (Vec::new(), Vec::new());
while row_i > 0 || col_i > 0 {
match table[row_i][col_i].1 {
Direction::Diagonal => {
aligned_x.push(x[col_i - 1]);
aligned_y.push(y[row_i - 1]);
row_i -= 1;
col_i -= 1;
}
Direction::Left => {
aligned_x.push(x[col_i - 1]);
aligned_y.push(b'-');
col_i -= 1;
}
Direction::Up => {
aligned_x.push(b'-');
aligned_y.push(y[row_i - 1]);
row_i -= 1;
}
}
}
aligned_x.reverse();
aligned_y.reverse();
let aligned_x = String::from_utf8(aligned_x).unwrap_or_else(|_| unreachable!("We know we added valid characters."));
let aligned_y = String::from_utf8(aligned_y).unwrap_or_else(|_| unreachable!("We know we added valid characters."));
(aligned_x, aligned_y)
}
#[must_use]
pub fn trace_back_recursive<U: UInt>(table: &[Vec<(U, Direction)>], [x, y]: [&str; 2]) -> (String, String) {
let (mut aligned_x, mut aligned_y) = (Vec::new(), Vec::new());
_trace_back_recursive(
table,
[y.len(), x.len()],
[x.as_bytes(), y.as_bytes()],
[&mut aligned_x, &mut aligned_y],
);
aligned_x.reverse();
aligned_y.reverse();
let aligned_x = String::from_utf8(aligned_x).unwrap_or_else(|_| unreachable!("We know we added valid characters."));
let aligned_y = String::from_utf8(aligned_y).unwrap_or_else(|_| unreachable!("We know we added valid characters."));
(aligned_x, aligned_y)
}
fn _trace_back_recursive<U: UInt>(
table: &[Vec<(U, Direction)>],
[mut row_i, mut col_i]: [usize; 2],
[x, y]: [&[u8]; 2],
[aligned_x, aligned_y]: [&mut Vec<u8>; 2],
) {
if row_i > 0 || col_i > 0 {
match table[row_i][col_i].1 {
Direction::Diagonal => {
aligned_x.push(x[col_i - 1]);
aligned_y.push(y[row_i - 1]);
row_i -= 1;
col_i -= 1;
}
Direction::Left => {
aligned_x.push(x[col_i - 1]);
aligned_y.push(b'-');
col_i -= 1;
}
Direction::Up => {
aligned_x.push(b'-');
aligned_y.push(y[row_i - 1]);
row_i -= 1;
}
};
_trace_back_recursive(table, [row_i, col_i], [x, y], [aligned_x, aligned_y]);
}
}
#[must_use]
pub fn compute_edits(x: &str, y: &str) -> [Vec<Edit>; 2] {
[_x_to_y(x, y), _x_to_y(y, x)]
}
#[must_use]
pub fn _x_to_y(x: &str, y: &str) -> Vec<Edit> {
x.chars()
.zip(y.chars())
.enumerate()
.filter(|(_, (x, y))| x != y)
.map(|(i, (_, y))| Edit::Sub(i, y))
.collect()
}
#[must_use]
pub fn unaligned_x_to_y(x: &str, y: &str) -> Vec<Edit> {
let mut unaligned_x_to_y = Vec::new();
let mut modifier = 0;
x.chars()
.zip(y.chars())
.enumerate()
.filter(|(_, (x, y))| x != y)
.for_each(|(index, (c_x, c_y))| {
let i = index - modifier;
if c_x == '-' {
unaligned_x_to_y.push(Edit::Ins(i, c_y));
} else if c_y == '-' {
unaligned_x_to_y.push(Edit::Del(i));
modifier += 1;
} else {
unaligned_x_to_y.push(Edit::Sub(i, c_y));
}
});
unaligned_x_to_y
}
#[must_use]
pub fn aligned_x_to_y(x: &str, y: &str) -> Vec<Edit> {
let table = compute_table::<u16>(x, y, Penalties::default());
let (aligned_x, aligned_y) = trace_back_iterative(&table, [x, y]);
let mut aligned_x_to_y: Vec<Edit> = Vec::new();
let mut modifier = 0;
aligned_x
.chars()
.zip(aligned_y.chars())
.enumerate()
.filter(|(_, (aligned_x, aligned_y))| aligned_x != aligned_y)
.for_each(|(index, (c_x, c_y))| {
let i = index - modifier;
if c_x == '-' {
aligned_x_to_y.push(Edit::Ins(i, c_y));
} else if c_y == '-' {
aligned_x_to_y.push(Edit::Del(i));
modifier += 1;
} else {
aligned_x_to_y.push(Edit::Sub(i, c_y));
}
});
aligned_x_to_y
}
#[must_use]
pub fn aligned_x_to_y_no_sub(x: &str, y: &str) -> Vec<Edit> {
let table = compute_table::<u16>(x, y, Penalties::default());
let (aligned_x, aligned_y) = trace_back_iterative(&table, [x, y]);
let mut aligned_x_to_y: Vec<Edit> = Vec::new();
let mut modifier = 0;
aligned_x
.chars()
.zip(aligned_y.chars())
.enumerate()
.filter(|(_, (aligned_x, aligned_y))| aligned_x != aligned_y)
.for_each(|(index, (c_x, c_y))| {
let i = index - modifier;
if c_x == '-' {
aligned_x_to_y.push(Edit::Ins(i, c_y));
} else if c_y == '-' {
aligned_x_to_y.push(Edit::Del(i));
modifier += 1;
}
});
aligned_x_to_y
}
#[must_use]
pub fn x_to_y_alignment(x: &str, y: &str) -> [Vec<usize>; 2] {
let table = compute_table::<u16>(x, y, Penalties::default());
let (aligned_x, aligned_y) = trace_back_iterative(&table, [x, y]);
let mut gap_indices: [Vec<usize>; 2] = [Vec::new(), Vec::new()];
let mut modifier: usize = 0;
aligned_x
.chars()
.zip(aligned_y.chars())
.enumerate()
.filter(|(_, (aligned_x, aligned_y))| aligned_x != aligned_y)
.for_each(|(index, (c_x, c_y))| {
let i = index - modifier;
if c_x == '-' {
gap_indices[0].push(index);
} else if c_y == '-' {
gap_indices[1].push(i);
modifier += 1;
}
});
gap_indices
}
#[must_use]
pub fn apply_edits(x: &str, edits: &[Edit]) -> String {
let mut x: Vec<char> = x.chars().collect();
for edit in edits {
match edit {
Edit::Sub(i, c) => {
x[*i] = *c;
}
Edit::Ins(i, c) => {
x.insert(*i, *c);
}
Edit::Del(i) => {
x.remove(*i);
}
}
}
x.into_iter().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_table() {
let x = "NAJIBPEPPERSEATS";
let y = "NAJIBEATSPEPPERS";
let table = compute_table::<u16>(x, y, Penalties::default());
#[rustfmt::skip]
let true_table: [[(u16, Direction); 17]; 17] = [
[( 0, Direction::Diagonal), ( 1, Direction::Left ), ( 2, Direction::Left ), ( 3, Direction::Left ), ( 4, Direction::Left ), ( 5, Direction::Left ), ( 6, Direction::Left ), (7, Direction::Left ), (8, Direction::Left ), (9, Direction::Left ), (10, Direction::Left ), (11, Direction::Left ), (12, Direction::Left ), (13, Direction::Left ), (14, Direction::Left ), (15, Direction::Left ), (16, Direction::Left )],
[( 1, Direction::Up ), ( 0, Direction::Diagonal), ( 1, Direction::Left ), ( 2, Direction::Left ), ( 3, Direction::Left ), ( 4, Direction::Left ), ( 5, Direction::Left ), (6, Direction::Left ), (7, Direction::Left ), (8, Direction::Left ), ( 9, Direction::Left ), (10, Direction::Left ), (11, Direction::Left ), (12, Direction::Left ), (13, Direction::Left ), (14, Direction::Left ), (15, Direction::Left )],
[( 2, Direction::Up ), ( 1, Direction::Up ), ( 0, Direction::Diagonal), ( 1, Direction::Left ), ( 2, Direction::Left ), ( 3, Direction::Left ), ( 4, Direction::Left ), (5, Direction::Left ), (6, Direction::Left ), (7, Direction::Left ), ( 8, Direction::Left ), ( 9, Direction::Left ), (10, Direction::Left ), (11, Direction::Left ), (12, Direction::Diagonal), (13, Direction::Left ), (14, Direction::Left )],
[( 3, Direction::Up ), ( 2, Direction::Up ), ( 1, Direction::Up ), ( 0, Direction::Diagonal), ( 1, Direction::Left ), ( 2, Direction::Left ), ( 3, Direction::Left ), (4, Direction::Left ), (5, Direction::Left ), (6, Direction::Left ), ( 7, Direction::Left ), ( 8, Direction::Left ), ( 9, Direction::Left ), (10, Direction::Left ), (11, Direction::Left ), (12, Direction::Left ), (13, Direction::Left )],
[( 4, Direction::Up ), ( 3, Direction::Up ), ( 2, Direction::Up ), ( 1, Direction::Up ), ( 0, Direction::Diagonal), ( 1, Direction::Left ), ( 2, Direction::Left ), (3, Direction::Left ), (4, Direction::Left ), (5, Direction::Left ), ( 6, Direction::Left ), ( 7, Direction::Left ), ( 8, Direction::Left ), ( 9, Direction::Left ), (10, Direction::Left ), (11, Direction::Left ), (12, Direction::Left )],
[( 5, Direction::Up ), ( 4, Direction::Up ), ( 3, Direction::Up ), ( 2, Direction::Up ), ( 1, Direction::Up ), ( 0, Direction::Diagonal), ( 1, Direction::Left ), (2, Direction::Left ), (3, Direction::Left ), (4, Direction::Left ), ( 5, Direction::Left ), ( 6, Direction::Left ), ( 7, Direction::Left ), ( 8, Direction::Left ), ( 9, Direction::Left ), (10, Direction::Left ), (11, Direction::Left )],
[( 6, Direction::Up ), ( 5, Direction::Up ), ( 4, Direction::Up ), ( 3, Direction::Up ), ( 2, Direction::Up ), ( 1, Direction::Up ), ( 1, Direction::Diagonal), (1, Direction::Diagonal), (2, Direction::Left ), (3, Direction::Left ), ( 4, Direction::Diagonal), ( 5, Direction::Left ), ( 6, Direction::Left ), ( 7, Direction::Diagonal), ( 8, Direction::Left ), ( 9, Direction::Left ), (10, Direction::Left )],
[( 7, Direction::Up ), ( 6, Direction::Up ), ( 5, Direction::Diagonal), ( 4, Direction::Up ), ( 3, Direction::Up ), ( 2, Direction::Up ), ( 2, Direction::Diagonal), (2, Direction::Diagonal), (2, Direction::Diagonal), (3, Direction::Diagonal), ( 4, Direction::Diagonal), ( 5, Direction::Diagonal), ( 6, Direction::Diagonal), ( 7, Direction::Diagonal), ( 7, Direction::Diagonal), ( 8, Direction::Left ), ( 9, Direction::Left )],
[( 8, Direction::Up ), ( 7, Direction::Up ), ( 6, Direction::Up ), ( 5, Direction::Up ), ( 4, Direction::Up ), ( 3, Direction::Up ), ( 3, Direction::Diagonal), (3, Direction::Diagonal), (3, Direction::Diagonal), (3, Direction::Diagonal), ( 4, Direction::Diagonal), ( 5, Direction::Diagonal), ( 6, Direction::Diagonal), ( 7, Direction::Diagonal), ( 8, Direction::Diagonal), ( 7, Direction::Diagonal), ( 8, Direction::Left )],
[( 9, Direction::Up ), ( 8, Direction::Up ), ( 7, Direction::Up ), ( 6, Direction::Up ), ( 5, Direction::Up ), ( 4, Direction::Up ), ( 4, Direction::Diagonal), (4, Direction::Diagonal), (4, Direction::Diagonal), (4, Direction::Diagonal), ( 4, Direction::Diagonal), ( 5, Direction::Diagonal), ( 5, Direction::Diagonal), ( 6, Direction::Left ), ( 7, Direction::Left ), ( 8, Direction::Up ), ( 7, Direction::Diagonal)],
[(10, Direction::Up ), ( 9, Direction::Up ), ( 8, Direction::Up ), ( 7, Direction::Up ), ( 6, Direction::Up ), ( 5, Direction::Up ), ( 4, Direction::Diagonal), (5, Direction::Diagonal), (4, Direction::Diagonal), (4, Direction::Diagonal), ( 5, Direction::Diagonal), ( 5, Direction::Diagonal), ( 6, Direction::Diagonal), ( 6, Direction::Diagonal), ( 7, Direction::Diagonal), ( 8, Direction::Diagonal), ( 8, Direction::Up )],
[(11, Direction::Up ), (10, Direction::Up ), ( 9, Direction::Up ), ( 8, Direction::Up ), ( 7, Direction::Up ), ( 6, Direction::Up ), ( 5, Direction::Up ), (4, Direction::Diagonal), (5, Direction::Up ), (5, Direction::Diagonal), ( 4, Direction::Diagonal), ( 5, Direction::Left ), ( 6, Direction::Diagonal), ( 6, Direction::Diagonal), ( 7, Direction::Diagonal), ( 8, Direction::Diagonal), ( 9, Direction::Diagonal)],
[(12, Direction::Up ), (11, Direction::Up ), (10, Direction::Up ), ( 9, Direction::Up ), ( 8, Direction::Up ), ( 7, Direction::Up ), ( 6, Direction::Diagonal), (5, Direction::Up ), (4, Direction::Diagonal), (5, Direction::Diagonal), ( 5, Direction::Up ), ( 5, Direction::Diagonal), ( 6, Direction::Diagonal), ( 7, Direction::Diagonal), ( 7, Direction::Diagonal), ( 8, Direction::Diagonal), ( 9, Direction::Diagonal)],
[(13, Direction::Up ), (12, Direction::Up ), (11, Direction::Up ), (10, Direction::Up ), ( 9, Direction::Up ), ( 8, Direction::Up ), ( 7, Direction::Diagonal), (6, Direction::Up ), (5, Direction::Diagonal), (4, Direction::Diagonal), ( 5, Direction::Left ), ( 6, Direction::Diagonal), ( 6, Direction::Diagonal), ( 7, Direction::Diagonal), ( 8, Direction::Diagonal), ( 8, Direction::Diagonal), ( 9, Direction::Diagonal)],
[(14, Direction::Up ), (13, Direction::Up ), (12, Direction::Up ), (11, Direction::Up ), (10, Direction::Up ), ( 9, Direction::Up ), ( 8, Direction::Up ), (7, Direction::Diagonal), (6, Direction::Up ), (5, Direction::Up ), ( 4, Direction::Diagonal), ( 5, Direction::Left ), ( 6, Direction::Left ), ( 6, Direction::Diagonal), ( 7, Direction::Left ), ( 8, Direction::Left ), ( 9, Direction::Diagonal)],
[(15, Direction::Up ), (14, Direction::Up ), (13, Direction::Up ), (12, Direction::Up ), (11, Direction::Up ), (10, Direction::Up ), ( 9, Direction::Up ), (8, Direction::Up ), (7, Direction::Up ), (6, Direction::Up ), ( 5, Direction::Up ), ( 4, Direction::Diagonal), ( 5, Direction::Left ), ( 6, Direction::Left ), ( 7, Direction::Diagonal), ( 8, Direction::Diagonal), ( 9, Direction::Diagonal)],
[(16, Direction::Up ), (15, Direction::Up ), (14, Direction::Up ), (13, Direction::Up ), (12, Direction::Up ), (11, Direction::Up ), (10, Direction::Up ), (9, Direction::Up ), (8, Direction::Up ), (7, Direction::Up ), ( 6, Direction::Up ), ( 5, Direction::Up ), ( 4, Direction::Diagonal), ( 5, Direction::Left ), ( 6, Direction::Left ), ( 7, Direction::Left ), ( 8, Direction::Diagonal)]
];
assert_eq!(table, true_table);
}
#[test]
fn test_trace_back() {
let peppers_x = "NAJIBPEPPERSEATS";
let peppers_y = "NAJIBEATSPEPPERS";
let peppers_table = compute_table::<u16>(peppers_x, peppers_y, Penalties::default());
let (aligned_x, aligned_y) = trace_back_recursive(&peppers_table, [peppers_x, peppers_y]);
assert_eq!(aligned_x, "NAJIB-PEPPERSEATS");
assert_eq!(aligned_y, "NAJIBEATSPEPPE-RS");
let (aligned_x, aligned_y) = trace_back_iterative(&peppers_table, [peppers_x, peppers_y]);
assert_eq!(aligned_x, "NAJIB-PEPPERSEATS");
assert_eq!(aligned_y, "NAJIBEATSPEPPE-RS");
let guilty_x = "NOTGUILTY";
let guilty_y = "NOTGUILTY";
let guilty_table = compute_table::<u16>(guilty_x, guilty_y, Penalties::default());
let (aligned_x, aligned_y) = trace_back_recursive(&guilty_table, [guilty_x, guilty_y]);
assert_eq!(aligned_x, "NOTGUILTY");
assert_eq!(aligned_y, "NOTGUILTY");
let (aligned_x, aligned_y) = trace_back_iterative(&guilty_table, [guilty_x, guilty_y]);
assert_eq!(aligned_x, "NOTGUILTY");
assert_eq!(aligned_y, "NOTGUILTY");
}
}