use std::collections::BTreeMap;
use crate::Op;
use crate::assign::assign;
pub(crate) fn normalize(ops: &mut [Op]) {
let mut start = 0;
while start < ops.len() {
if !is_edit(&ops[start]) {
start += 1;
continue;
}
let end = start + ops[start..].iter().take_while(|op| is_edit(op)).count();
ops[start..end].sort_by_key(|op| matches!(op, Op::Insert { .. }));
start = end;
}
}
fn is_edit(op: &Op) -> bool {
matches!(op, Op::Delete { .. } | Op::Insert { .. })
}
pub(crate) fn detect_moves(
ops: Vec<Op>,
keys_a: &[u32],
keys_b: &[u32],
cost: &dyn Fn(usize, usize) -> f64,
) -> Vec<Op> {
let mut deleted: BTreeMap<u32, Vec<usize>> = BTreeMap::new();
let mut inserted: BTreeMap<u32, Vec<usize>> = BTreeMap::new();
for op in &ops {
match *op {
Op::Delete { a } => deleted.entry(keys_a[a]).or_default().push(a),
Op::Insert { b } => inserted.entry(keys_b[b]).or_default().push(b),
_ => {}
}
}
let mut pairs = Vec::new();
for (key, rows) in &deleted {
if let Some(cols) = inserted.get(key) {
pairs.extend(assign(rows, cols, cost));
}
}
replace_pairs(ops, &pairs, |a, b| Op::Move { a, b })
}
fn replace_pairs(
ops: Vec<Op>,
pairs: &[(usize, usize)],
make: impl Fn(usize, usize) -> Op,
) -> Vec<Op> {
if pairs.is_empty() {
return ops;
}
let partner_of_b: BTreeMap<usize, usize> = pairs.iter().map(|&(a, b)| (b, a)).collect();
let paired_a: std::collections::BTreeSet<usize> = pairs.iter().map(|&(a, _)| a).collect();
ops.into_iter()
.filter_map(|op| match op {
Op::Delete { a } if paired_a.contains(&a) => None,
Op::Insert { b } => Some(match partner_of_b.get(&b) {
Some(&a) => make(a, b),
None => op,
}),
op => Some(op),
})
.collect()
}
pub(crate) fn semantic_cleanup(ops: &mut Vec<Op>, min_equal_run: usize) {
loop {
let mut changed = false;
let mut out = Vec::with_capacity(ops.len());
let mut i = 0;
while i < ops.len() {
if !matches!(ops[i], Op::Equal { .. }) {
out.push(ops[i].clone());
i += 1;
continue;
}
let end = i + ops[i..]
.iter()
.take_while(|op| matches!(op, Op::Equal { .. }))
.count();
let sandwiched = i > 0 && end < ops.len() && is_edit(&ops[i - 1]) && is_edit(&ops[end]);
if sandwiched && end - i < min_equal_run {
for op in &ops[i..end] {
if let Op::Equal { a, b } = *op {
out.push(Op::Delete { a });
out.push(Op::Insert { b });
}
}
changed = true;
} else {
out.extend_from_slice(&ops[i..end]);
}
i = end;
}
normalize(&mut out);
*ops = out;
if !changed {
return;
}
}
}
pub(crate) fn pair_adjacent(ops: Vec<Op>) -> Vec<Op> {
let mut out = Vec::with_capacity(ops.len());
let mut i = 0;
while i < ops.len() {
let Op::Delete { a: a0 } = ops[i] else {
out.push(ops[i].clone());
i += 1;
continue;
};
let dels = contiguous(&ops[i..], a0, |op| match *op {
Op::Delete { a } => Some(a),
_ => None,
});
let ins = match ops.get(i + dels) {
Some(&Op::Insert { b: b0 }) => {
let n = contiguous(&ops[i + dels..], b0, |op| match *op {
Op::Insert { b } => Some(b),
_ => None,
});
Some((b0, n))
}
_ => None,
};
match ins {
Some((b0, n)) => {
out.push(Op::Replace {
a: a0..a0 + dels,
b: b0..b0 + n,
});
i += dels + n;
}
None => {
out.extend_from_slice(&ops[i..i + dels]);
i += dels;
}
}
}
out
}
fn contiguous(ops: &[Op], start: usize, index: impl Fn(&Op) -> Option<usize>) -> usize {
ops.iter()
.zip(start..)
.take_while(|(op, want)| index(op) == Some(*want))
.count()
}
pub(crate) fn pair_cross_hunk(
ops: Vec<Op>,
class_a: &[Option<u32>],
class_b: &[Option<u32>],
cost: &dyn Fn(usize, usize) -> f64,
) -> Vec<Op> {
let mut deleted: BTreeMap<u32, Vec<usize>> = BTreeMap::new();
let mut inserted: BTreeMap<u32, Vec<usize>> = BTreeMap::new();
for op in &ops {
match *op {
Op::Delete { a } => {
if let Some(c) = class_a[a] {
deleted.entry(c).or_default().push(a);
}
}
Op::Insert { b } => {
if let Some(c) = class_b[b] {
inserted.entry(c).or_default().push(b);
}
}
_ => {}
}
}
let mut pairs = Vec::new();
for (class, rows) in &deleted {
if let Some(cols) = inserted.get(class) {
pairs.extend(assign(rows, cols, cost));
}
}
replace_pairs(ops, &pairs, |a, b| Op::Replace {
a: a..a + 1,
b: b..b + 1,
})
}