Compare commits

...

3 commits

Author SHA1 Message Date
4793c04241
wip 2024-08-30 08:09:12 +02:00
99d3ada2ba
wip 2024-08-29 22:03:18 +02:00
061a40a3ab
wip 2024-08-29 17:21:44 +02:00
8 changed files with 524 additions and 301 deletions

View file

@ -13,3 +13,4 @@ range-ext = "0.3.0"
[dev-dependencies]
proptest = "1.2.0"
rayon = "1.0.0"
rstest = "0.22.0"

View file

@ -6,7 +6,7 @@ use std::{
use range_ext::intersect::{Intersect, IntersectionExt};
/// Operations to apply to a vector
#[derive(Debug, Clone)]
#[derive(Clone)]
pub enum Operation<T> {
/// No operation
Nop,
@ -116,8 +116,13 @@ impl<T> Operation<T> {
vec.splice(pos..(pos + n), None);
}
Operation::Mov { pos, n, to } => {
// If items are moved within the source range, the operation has no effect
if to > pos && to < pos + n {
return;
}
let buf = (0..n).map(|_| vec.remove(pos)).collect::<Vec<_>>();
splice_or_append(vec, to, buf);
let n_to = if to > pos { to - n } else { to };
splice_or_append(vec, n_to, buf);
}
Operation::Seq { ops } => ops.into_iter().for_each(|op| op.apply(vec)),
}
@ -203,6 +208,266 @@ impl<T> Operation<T> {
println!();
v2
}
/// Print the operation as it is applied on the given vector
pub fn print_on_string(&self, vec: &[T]) -> (Vec<T>, String)
where
T: Display + Clone,
{
if let Operation::Seq { ops } = self {
let mut v = vec.to_vec();
for op in ops {
v = op.print_on(&v)
}
return v;
}
if matches!(self, Operation::Nop) {
print!(" NOP:");
for itm in vec {
print!(" {itm} ");
}
println!();
return vec.to_vec();
}
let r = self.range();
let ins = matches!(self, Operation::Ins { .. });
print!("FROM:");
for (i, itm) in vec.iter().enumerate() {
if i == r.start {
if ins {
print!("|");
} else {
print!("(");
}
} else {
print!(" ");
}
print!("{itm}");
if i == r.end.saturating_sub(1) && !ins {
print!(")");
} else {
print!(" ");
}
}
println!();
let r2 = self.target_range();
let del = matches!(self, Operation::Del { .. });
let mut v2 = vec.to_vec();
self.clone().apply(&mut v2);
print!(" TO:");
for (i, itm) in v2.iter().enumerate() {
if i == r2.start {
if del {
print!("|");
} else {
print!("(");
}
} else {
print!(" ");
}
print!("{itm}");
if i == r2.end.saturating_sub(1) && !del {
print!(")");
} else {
print!(" ");
}
}
println!();
v2
}
fn sanitize(&mut self) {
if self.is_empty() {
*self = Operation::Nop;
return;
}
if let Operation::Mov { pos, n, to } = self {
// Invalid move operation
if *to >= *pos && *to <= *pos + *n {
*self = Operation::Nop;
return;
}
}
}
/// During mov/del transformations with multiple items, mov operations are split
/// into multiple 1-item long operations.
///
/// After transformation, these can be merged together if the positions of subsequent
/// operations line up.
///
/// The function also collapses nested sequences and removes null operations.
fn consolidate_seq(&mut self) {
if let Operation::Seq { ops } = self {
struct MovTmp {
pos: usize,
to: usize,
n: usize,
dir: Option<bool>,
}
struct DelTmp {
pos: usize,
n: usize,
dir: Option<bool>,
}
let mut mov_tmp: Option<MovTmp> = None;
let mut del_tmp: Option<DelTmp> = None;
let mut buf = Vec::new();
fn do_push<T>(
buf: &mut Vec<Operation<T>>,
op: Option<Operation<T>>,
mov_tmp: &mut Option<MovTmp>,
del_tmp: &mut Option<DelTmp>,
) {
if let Some(dt) = del_tmp {
buf.push(Operation::Del {
pos: dt.pos,
n: dt.n,
});
*del_tmp = None;
}
if let Some(mt) = mov_tmp {
buf.push(Operation::Mov {
pos: if mt.dir.unwrap_or_default() {
mt.pos + 1 - mt.n
} else {
mt.pos
},
n: mt.n,
to: if mt.dir.unwrap_or_default() {
mt.to + 1 - mt.n
} else {
mt.to
},
});
*mov_tmp = None;
}
if let Some(op) = op {
buf.push(op);
}
}
fn proc_op<T>(
buf: &mut Vec<Operation<T>>,
op: Operation<T>,
mov_tmp: &mut Option<MovTmp>,
del_tmp: &mut Option<DelTmp>,
) {
match op {
Operation::Ins { .. } => do_push(buf, Some(op), mov_tmp, del_tmp),
Operation::Del { pos, n } => {
if let Some(dt) = del_tmp {
if dt.dir.unwrap_or(true) && pos == dt.pos {
// delete from left to right (n times same index)
dt.dir = Some(true);
dt.n += n;
} else if !dt.dir.unwrap_or_default() && pos + n == dt.pos {
// delete from right to left (i-n)
dt.dir = Some(false);
dt.pos = pos;
dt.n += n;
} else {
do_push(buf, None, mov_tmp, del_tmp);
*del_tmp = Some(DelTmp { pos, n, dir: None });
}
} else {
*del_tmp = Some(DelTmp { pos, n, dir: None });
}
}
Operation::Mov { pos, n, to } => {
if n == 1 {
if let Some(mt) = mov_tmp {
if mt.dir.unwrap_or(true)
&& mt.pos.checked_sub(mt.n).is_some_and(|p| pos == p)
&& mt.to.checked_sub(mt.n).is_some_and(|t| to == t)
{
// move right (to > pos)
mt.dir = Some(true);
mt.n += 1;
} else if !mt.dir.unwrap_or(false)
&& pos == mt.pos + mt.n
&& to == mt.to + mt.n
{
// move left (to <= pos)
mt.dir = Some(false);
mt.n += 1;
} else {
do_push(buf, None, mov_tmp, del_tmp);
*mov_tmp = Some(MovTmp {
pos,
to,
n: 1,
dir: None,
});
}
} else {
*mov_tmp = Some(MovTmp {
pos,
to,
n: 1,
dir: None,
});
}
} else {
do_push(buf, Some(op), mov_tmp, del_tmp);
}
}
Operation::Seq { ops } => {
ops.into_iter()
.for_each(|op| proc_op(buf, op, mov_tmp, del_tmp));
}
Operation::Nop => {}
}
}
ops.reverse();
while let Some(op) = ops.pop() {
proc_op(&mut buf, op, &mut mov_tmp, &mut del_tmp);
}
do_push(&mut buf, None, &mut mov_tmp, &mut del_tmp);
*self = if buf.is_empty() {
Operation::Nop
} else if buf.len() == 1 {
buf.pop().unwrap()
} else {
Operation::Seq { ops: buf }
};
}
}
}
impl<T> Debug for Operation<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Nop => write!(f, "Nop"),
Self::Ins { pos, val } => f
.debug_struct("Ins")
.field("pos", pos)
.field("val", &val.len())
.finish(),
Self::Del { pos, n } => f
.debug_struct("Del")
.field("pos", pos)
.field("n", n)
.finish(),
Self::Mov { pos, n, to } => f
.debug_struct("Mov")
.field("pos", pos)
.field("n", n)
.field("to", to)
.finish(),
Self::Seq { ops } => f.debug_struct("Seq").field("ops", ops).finish(),
}
}
}
/// Transform the given pair of operations in-place
@ -217,11 +482,14 @@ impl<T> Operation<T> {
/// ```
pub fn transform<T1, T2>(a: &mut Operation<T1>, b: &mut Operation<T2>) {
transform_internal(a, b);
consolidate_seq(a);
consolidate_seq(b);
// a.consolidate_seq();
// b.consolidate_seq();
}
fn transform_internal<T1, T2>(a: &mut Operation<T1>, b: &mut Operation<T2>) {
a.sanitize();
b.sanitize();
match a {
Operation::Nop => {}
Operation::Ins { pos: p1, val: v1 } => match b {
@ -341,101 +609,136 @@ fn transform_mov<T1, T2>(a: &mut Operation<T1>, b: &mut Operation<T2>, first: bo
to: m_to,
} = a
{
if *m_n != 1 && matches!(b, Operation::Del { .. } | Operation::Mov { .. }) {
/*
if *m_n != 1 && matches!(b, Operation::Mov { .. }) {
let new_a = mov2seq(*m_pos, *m_n, *m_to);
*a = new_a;
return transform_internal(a, b);
}
*/
let m_to_right = m_to > m_pos;
let m_to_start = if m_to_right { *m_to - *m_n } else { *m_to };
let rtarget = m_to_start..(m_to_start + *m_n);
match b {
Operation::Ins { pos: i_pos, val } => {
// Is the moved range split by the insert?
// Is the MOV source split by the insert?
if *i_pos > ra.start && *i_pos < ra.end {
let m_delta = *m_to as i64 - *m_pos as i64;
let m_delta = m_to_start as i64 - *m_pos as i64;
*i_pos = (*i_pos as i64 + m_delta) as usize;
*m_n += val.len();
return;
if m_to_right {
*m_to += val.len();
}
} else {
let mut del = Operation::Del::<()> {
pos: *m_pos,
n: *m_n,
};
let mut ins = Operation::Ins::<()> {
pos: m_to_start,
val: vec![(); *m_n],
};
if first {
transform_internal(&mut del, b);
transform_internal(&mut ins, b);
} else {
transform_internal(b, &mut del);
transform_internal(b, &mut ins);
}
*m_pos = del.pos();
*m_to = ins.pos();
if m_to_right {
*m_to += *m_n;
}
if m_pos == m_to {
*a = Operation::Nop;
}
}
}
Operation::Del { pos: d_pos, n } => {
let rb = *d_pos..(*d_pos + *n);
// Delete move source
if ra == rb {
// Delete all moved items in A'
*d_pos = *m_to;
*a = Operation::Nop;
return;
}
// within src - within dst
match (rb.contains(m_pos), rb.contains(m_to)) {
(true, false) => {
let p_rest = if m_to <= d_pos { *d_pos + 1 } else { *d_pos };
let mut ops = vec![
// Delete moved item
Operation::Del { pos: *m_to, n: 1 },
// Delete rest
Operation::Del {
pos: p_rest,
n: *n - 1,
},
];
if *m_to < p_rest {
ops.swap(0, 1);
Operation::Del { pos: d_pos, n: d_n } => {
let rb = *d_pos..(*d_pos + *d_n);
let (iba, ibt) = (rb.intersect_ext(&ra), rb.intersect_ext(&rtarget));
// delete within src - delete within dst
match (&iba, &ibt) {
// Part of the source is deleted
(IntersectionExt::LessOverlap, _) => {
// MOV right end of DEL
let d_pos_split = *m_pos;
let rest_len = d_pos_split - *d_pos;
if m_to_right {
let del_ops = vec![
// Delete moved item
Operation::Del {
pos: m_to_start,
n: *d_n - rest_len,
},
// Delete rest
Operation::Del {
pos: *d_pos,
n: rest_len,
},
];
*m_pos = *d_pos;
*m_n = ra.end - rb.end;
*m_to -= *d_n;
*b = Operation::Seq { ops: del_ops };
} else {
if matches!(&ibt, IntersectionExt::Over | IntersectionExt::LessOverlap) {
unimplemented!("tmp")
}
let del_ops = vec![
// Delete rest
Operation::Del {
pos: *d_pos + *m_n,
n: rest_len,
},
// Delete moved item
Operation::Del {
pos: m_to_start,
n: *d_n - rest_len,
},
];
*m_pos = *d_pos;
*m_n = ra.end - rb.end;
*b = Operation::Seq { ops: del_ops };
}
*a = Operation::Nop;
*b = Operation::Seq { ops };
return;
}
(false, true) => {
let mut p_start = *d_pos;
let to_r = m_to > m_pos;
let del_before_src = d_pos < m_pos;
if !del_before_src {
p_start -= 1;
}
// Number of items to delete before moved item
let n1 = *m_to - *d_pos + usize::from(to_r);
*m_to -= n1;
if del_before_src {
*m_pos -= *n;
}
if m_pos == m_to {
if a.is_empty() {
*a = Operation::Nop;
}
if n1 == *n {
*d_pos = p_start;
} else if n1 == 0 {
*d_pos = p_start + 1;
} else {
*b = Operation::Seq {
ops: vec![
// Delete after moved item
Operation::Del {
pos: p_start + n1 + 1,
n: *n - n1,
},
// Delete before moved item
Operation::Del {
pos: p_start,
n: n1,
},
],
};
}
return;
}
(true, true) => {
// Within src and dst: delete whole range
(IntersectionExt::GreaterOverlap, _) => {
// MOV left end of DEL
let d_pos_split = *m_pos + *m_n;
unimplemented!("m_pos < d_pos");
}
// DEL middle of the MOV op
(IntersectionExt::Within, _) => {
unimplemented!("DEL middle of MOV op");
}
// DEL is split by the MOV op
(IntersectionExt::Over, _) => {
unimplemented!("DEL is split by the MOV op");
}
// Entire source is deleted
(IntersectionExt::Same, _) => {
// Delete all moved items in A'
*d_pos = m_to_start;
*a = Operation::Nop;
return;
}
(false, false) => {}
// Entire destination is deleted
// (_, Intersection::Full) => {}
(IntersectionExt::Empty, _) | (_, IntersectionExt::Empty) => {}
_ => unimplemented!("mov-del constellation {iba:?};{ibt:?}"),
}
}
Operation::Mov {
@ -443,234 +746,31 @@ fn transform_mov<T1, T2>(a: &mut Operation<T1>, b: &mut Operation<T2>, first: bo
n: b_n,
to: b_to,
} => {
if *b_n != 1 {
let new_b = mov2seq(*b_pos, *b_n, *b_to);
*b = new_b;
return transform_internal(a, b);
}
if m_pos == b_pos {
if first {
if b_to == m_to {
*b = Operation::Nop;
} else {
*b_pos = *m_to;
}
*a = Operation::Nop;
} else {
if m_to == b_to {
*a = Operation::Nop;
} else {
*m_pos = *b_to;
}
*b = Operation::Nop;
}
return;
}
unimplemented!("mov-mov");
}
Operation::Seq { ops } => return transform_seq(ops, a, false),
Operation::Nop => return,
}
let mut del = Operation::Del::<()> {
pos: *m_pos,
n: *m_n,
};
let mut ins = Operation::Ins::<()> {
pos: *m_to,
val: vec![(); *m_n],
};
if first {
transform_internal(&mut del, b);
transform_internal(&mut ins, b);
} else {
transform_internal(b, &mut del);
transform_internal(b, &mut ins);
}
*m_pos = del.pos();
*m_to = ins.pos();
if m_pos == m_to {
*a = Operation::Nop;
Operation::Seq { ops } => transform_seq(ops, a, false),
Operation::Nop => {}
}
}
}
fn mov2seq<T>(pos: usize, n: usize, to: usize) -> Operation<T> {
Operation::Seq {
ops: if to > pos {
(pos..pos + n)
ops: if to < pos {
(0..n)
.rev()
.map(|i| Operation::Mov {
pos: i,
.map(|_| Operation::Mov {
pos: pos + n - 1,
n: 1,
to: i + (to - pos),
to,
})
.collect()
} else {
(pos..pos + n)
.map(|i| Operation::Mov {
pos: i,
n: 1,
to: i - (pos - to),
})
.collect()
(0..n).map(|_| Operation::Mov { pos, n: 1, to }).collect()
},
}
}
/// During mov/del transformations with multiple items, mov operations are split
/// into multiple 1-item long operations.
///
/// After transformation, these can be merged together if the positions of subsequent
/// operations line up.
///
/// The function also collapses nested sequences and removes null operations.
fn consolidate_seq<T>(op: &mut Operation<T>) {
if let Operation::Seq { ops } = op {
struct MovTmp {
pos: usize,
to: usize,
n: usize,
dir: Option<bool>,
}
struct DelTmp {
pos: usize,
n: usize,
dir: Option<bool>,
}
let mut mov_tmp: Option<MovTmp> = None;
let mut del_tmp: Option<DelTmp> = None;
let mut buf = Vec::new();
fn do_push<T>(
buf: &mut Vec<Operation<T>>,
op: Option<Operation<T>>,
mov_tmp: &mut Option<MovTmp>,
del_tmp: &mut Option<DelTmp>,
) {
if let Some(dt) = del_tmp {
buf.push(Operation::Del {
pos: dt.pos,
n: dt.n,
});
*del_tmp = None;
}
if let Some(mt) = mov_tmp {
buf.push(Operation::Mov {
pos: if mt.dir.unwrap_or_default() {
mt.pos + 1 - mt.n
} else {
mt.pos
},
n: mt.n,
to: if mt.dir.unwrap_or_default() {
mt.to + 1 - mt.n
} else {
mt.to
},
});
*mov_tmp = None;
}
if let Some(op) = op {
buf.push(op);
}
}
fn proc_op<T>(
buf: &mut Vec<Operation<T>>,
op: Operation<T>,
mov_tmp: &mut Option<MovTmp>,
del_tmp: &mut Option<DelTmp>,
) {
match op {
Operation::Ins { .. } => do_push(buf, Some(op), mov_tmp, del_tmp),
Operation::Del { pos, n } => {
if let Some(dt) = del_tmp {
if dt.dir.unwrap_or(true) && pos == dt.pos {
// delete from left to right (n times same index)
dt.dir = Some(true);
dt.n += n;
} else if !dt.dir.unwrap_or_default() && pos + n == dt.pos {
// delete from right to left (i-n)
dt.dir = Some(false);
dt.pos = pos;
dt.n += n;
} else {
do_push(buf, None, mov_tmp, del_tmp);
*del_tmp = Some(DelTmp { pos, n, dir: None });
}
} else {
*del_tmp = Some(DelTmp { pos, n, dir: None });
}
}
Operation::Mov { pos, n, to } => {
if n == 1 {
if let Some(mt) = mov_tmp {
if mt.dir.unwrap_or(true)
&& mt.pos.checked_sub(mt.n).is_some_and(|p| pos == p)
&& mt.to.checked_sub(mt.n).is_some_and(|t| to == t)
{
// move right (to > pos)
mt.dir = Some(true);
mt.n += 1;
} else if !mt.dir.unwrap_or(false)
&& pos == mt.pos + mt.n
&& to == mt.to + mt.n
{
// move left (to <= pos)
mt.dir = Some(false);
mt.n += 1;
} else {
do_push(buf, None, mov_tmp, del_tmp);
*mov_tmp = Some(MovTmp {
pos,
to,
n: 1,
dir: None,
});
}
} else {
*mov_tmp = Some(MovTmp {
pos,
to,
n: 1,
dir: None,
});
}
} else {
do_push(buf, Some(op), mov_tmp, del_tmp);
}
}
Operation::Seq { ops } => {
ops.into_iter()
.for_each(|op| proc_op(buf, op, mov_tmp, del_tmp));
}
Operation::Nop => {}
}
}
ops.reverse();
while let Some(op) = ops.pop() {
proc_op(&mut buf, op, &mut mov_tmp, &mut del_tmp);
}
do_push(&mut buf, None, &mut mov_tmp, &mut del_tmp);
*op = if buf.is_empty() {
Operation::Nop
} else if buf.len() == 1 {
buf.pop().unwrap()
} else {
Operation::Seq { ops: buf }
};
}
}
fn splice_or_append<T>(v1: &mut Vec<T>, pos: usize, mut v2: Vec<T>) {
if pos < v1.len() {
v1.splice(pos..pos, v2);
@ -678,3 +778,23 @@ fn splice_or_append<T>(v1: &mut Vec<T>, pos: usize, mut v2: Vec<T>) {
v1.append(&mut v2);
}
}
#[cfg(test)]
mod tests {
use crate::mov2seq;
#[test]
fn t_mov2seq() {
let input = [0, 1, 2, 3, 4, 5, 6];
let a = mov2seq::<i32>(1, 3, 6);
let b = mov2seq::<i32>(3, 3, 1);
let mut ia = input.to_vec();
a.apply(&mut ia);
assert_eq!(ia, &[0, 4, 5, 1, 2, 3, 6]);
let mut ib = input.to_vec();
b.apply(&mut ib);
assert_eq!(ib, &[0, 3, 4, 5, 1, 2, 6]);
}
}

View file

@ -10,3 +10,7 @@ cc d4edfb3e7e78b85fe2a883940ac794afd21a3b2195aa932eb52b28cdccc2fdca # shrinks to
cc 892dbd5d37c6440c9edb3a9d3384c90dcb0a8f267204fdaf818ae9003313f418 # shrinks to a_pos = 472, a_to = 0, b_pos = 0, b_to = 0
cc 62dd5b6206b18bf74368878df5d2e23ae39ca636e8ba3b64e8108282fab1a016 # shrinks to a_pos = 0, a_to = 99, b_pos = 67, b_len = 33
cc 14266631a1edc4c03fa98541c29f6bcb23847ad082382c4d109bbf539520c3e0 # shrinks to a_pos = 61, a_to = 11, b_pos = 11, b_len = 50
cc 6dc10c336fa7355bd28d7711fdbdd8df6bbc535eee8d8c638114136d9d6bfe78 # shrinks to a_len = 28, a_pos = 8, a_to = 36, b_pos = 9, b_len = 1
cc 77f1e77a151931af800f28ab89798968068928ea15207dead56b2b84b1e7a6db # shrinks to a_len = 18, a_pos = 0, a_to = 58, b_pos = 59, b_len = 1
cc 848a2065da6479b78cfde677f86b967679752833b31caab9baa2abb3b5fae5c3 # shrinks to a_pos = 26, a_len = 16, a_to = 6, b_pos = 0, b_len = 27
cc 7264170514471bbf16d27edd8153c228e9e62bc11afbc156712f855bd51a80d9 # shrinks to a_pos = 14, a_len = 37, a_to = 13, b_pos = 13, b_len = 2

View file

@ -4,7 +4,7 @@ use proptest::prelude::*;
use rayon::prelude::*;
use otvec::Operation;
use util::test_transform_cond;
use util::{test_transform_cond, test_transform_cond_catch};
const ALL_SIZE: usize = 20;
const VEC_SIZE: usize = 100;
@ -94,8 +94,8 @@ fn t_mov_del(size: usize, a_pos: usize, a_len: usize, a_to: usize, b_pos: usize,
pos: b_pos,
n: b_len,
};
test_transform_cond(&a, &b, &input);
test_transform_cond(&b, &a, &input);
test_transform_cond_catch(&a, &b, &input);
test_transform_cond_catch(&b, &a, &input);
}
fn t_mov_mov(
@ -197,6 +197,7 @@ fn all_mov_del() {
});
}
/*
#[test]
fn all_mov_mov() {
(0..ALL_SIZE).into_par_iter().for_each(|a_pos| {
@ -219,6 +220,7 @@ fn all_mov_mov() {
});
});
}
*/
proptest! {
#[test]
@ -261,6 +263,7 @@ proptest! {
t_mov_del(VEC_SIZE, a_pos, a_len, a_to, b_pos, b_len)
}
/*
#[test]
fn transform_mov_mov(a_pos in 0usize..VEC_SIZE, a_len in 0usize..VEC_SIZE, a_to in 0usize..VEC_SIZE, b_pos in 0usize..VEC_SIZE, b_len in 1usize..VEC_SIZE, b_to in 0usize..VEC_SIZE) {
// Limit inputs
@ -271,4 +274,5 @@ proptest! {
t_mov_mov(VEC_SIZE, a_pos, a_len, a_to, b_pos, b_len, b_to)
}
*/
}

View file

@ -3,6 +3,19 @@ mod util;
use otvec::Operation;
use util::{test_transform, test_transform_sym};
use rstest::rstest;
#[rstest]
#[case(0, 1, 0, &[1, 2, 3, 4, 5])]
#[case(0, 1, 1, &[1, 2, 3, 4, 5])]
#[case(0, 1, 2, &[2, 1, 3, 4, 5])]
fn apply_mov(#[case] pos: usize, #[case] n: usize, #[case] to: usize, #[case] expect: &[i32]) {
let mut input = vec![1, 2, 3, 4, 5];
let op = Operation::Mov { pos, n, to };
op.apply(&mut input);
assert_eq!(input, expect);
}
#[test]
fn transform_ins_ins() {
let a = Operation::Ins {

39
tests/tests_mov.rs Normal file
View file

@ -0,0 +1,39 @@
mod util;
use otvec::Operation;
use util::test_transform_sym;
use rstest::rstest;
// MOV: [0, 1, 2, 3, 4, 5, 6, 7, 8] => [0, 1, 2, 3, 4, 5, 6, 7, 8]
// DEL: [0, 1, 2, 3, 4, 5, 6, 7, 8] => [0, 1, 2, 3, 4, 5, 6, 7, 8]
#[rstest]
// MOV-DEL; Intersection::Overlap, Intersection::Empty; right end; to right
// MOV: [0, (1, 2, 3), 4, 5, 6, 7, 8] => [0, 4, 5, (1, 2, 3), 6, 7, 8]
// DEL: [(0, 1), 2, 3, 4, 5, 6, 7, 8] => [2, 3, 4, 5, 6, 7, 8]
#[case(Operation::Mov {pos: 1, n: 3, to: 6}, Operation::Del {pos: 0, n: 2}, &[4, 5, 2, 3, 6, 7, 8])]
// MOV-DEL; Intersection::Overlap, Intersection::Empty; right end; to left
// MOV: [0, 1, 2, (3, 4, 5), 6, 7, 8] => [0, (3, 4, 5), 1, 2, 6, 7, 8]
// DEL: [0, 1, (2, 3), 4, 5, 6, 7, 8] => [0, 1, 4, 5, 6, 7, 8]
#[case(Operation::Mov {pos: 3, n: 3, to: 1}, Operation::Del {pos: 2, n: 2}, &[0, 4, 5, 1, 6, 7, 8])]
// current debugging
// MOV-DEL; Intersection::Overlap, Intersection::Over;
// MOV: [0, 1, 2, 3, 4, (5, 6, 7), 8] => [0, 1, 5, 6, 7, 2, 3, 4, 8]
// DEL: [0, (1, 2, 3, 4, 5), 6, 7, 8] => [0, 6, 7, 8]
#[case(Operation::Mov {pos: 5, n: 3, to: 2}, Operation::Del {pos: 1, n: 5}, &[0, 6, 7, 8])]
// MOV-DEL; Intersection::Overlap, Intersection::Over;
// MOV: [0, 1, (2, 3, 4, 5, 6), 7, 8] => [0, 2, 3, 4, 5, 6, 1, 7, 8]
// DEL: [0, 1, 2, 3, 4, 5, 6, 7, 8] => [3, 4, 5, 6, 7, 8]
#[case(Operation::Mov {pos: 2, n: 5, to: 1}, Operation::Del {pos: 0, n: 3}, &[0, 6, 7, 8])]
// MOV-DEL; Intersection::Overlap, Intersection::Empty; left end; to left
// MOV: [0, 1, 2, (3, 4, 5), 6, 7, 8] => [0, (3, 4, 5), 1, 2, 6, 7, 8]
// DEL: [0, 1, 2, 3, 4, (5, 6), 7, 8] => [0, 1, 2, 3, 4, 7, 8]
#[case(Operation::Mov {pos: 3, n: 3, to: 1}, Operation::Del {pos: 5, n: 2}, &[0, 6, 7, 1, 2, 3, 8])]
fn transform(#[case] a: Operation<i32>, #[case] b: Operation<i32>, #[case] expect: &[i32]) {
let input = [0, 1, 2, 3, 4, 5, 6, 7, 8];
test_transform_sym(&a, &b, &input, expect);
}

View file

@ -24,7 +24,7 @@ fn transform_mov_ins_mult_above() {
let a = Operation::Mov {
pos: 1,
n: 2,
to: 3,
to: 5,
};
let b = Operation::Ins {
pos: 3,
@ -39,7 +39,7 @@ fn transform_mov_ins_mult_split() {
let a = Operation::Mov {
pos: 1,
n: 2,
to: 3,
to: 5,
};
// [1, 2, 3, 4, 5] => [1, 2, (7, 8, 9), 3, 4, 5]
@ -63,7 +63,7 @@ fn transform_mov_ins_mult_split2() {
let a = Operation::Mov {
pos: 1,
n: 2,
to: 4,
to: 6,
};
// [1, 2, 3, 4, 5, 6, 0] => [1, 2, (7, 8, 9), 3, 4, 5, 6, 0]
@ -89,7 +89,7 @@ fn transform_mov_ins_mult_split3() {
let a = Operation::Mov {
pos: 1,
n: 3,
to: 3,
to: 6,
};
// [1, 2, 3, 4, 5, 6, 0] => [1, 2, (7, 8, 9), 3, 4, 5, 6, 0]
@ -140,7 +140,7 @@ fn transform_mov_ins_mult_same() {
let a = Operation::Mov {
pos: 0,
n: 2,
to: 3,
to: 5,
};
let b = Operation::Ins {
pos: 0,
@ -155,7 +155,7 @@ fn transform_mov_ins_mult_within() {
let a = Operation::Mov {
pos: 0,
n: 3,
to: 2,
to: 5,
};
// [1, 2, 3, 4, 5] => [1, 8, 9, 2, 3, 4, 5]
let b = Operation::Ins {
@ -170,7 +170,7 @@ fn transform_mov_ins_mult_over() {
let a = Operation::Mov {
pos: 1,
n: 2,
to: 2,
to: 4,
};
let b = Operation::Ins {
pos: 1,
@ -179,6 +179,7 @@ fn transform_mov_ins_mult_over() {
test_transform_sym(&a, &b, &[1, 2, 3, 4, 5], &[1, 6, 7, 8, 4, 2, 3, 5]);
}
/*
// MOV - DEL
#[test]
@ -434,16 +435,30 @@ fn transform_mov_del_below_overlap_l4() {
#[test]
fn transform_mov_del_above_overlap_r() {
// [0, (1, 2, 3), 4, 5, 6] => [0, 4, 5, (1, 2, 3), 6]
let a = Operation::Mov {
pos: 1,
n: 3,
to: 3,
to: 6,
};
// [(0, 1), 2, 3, 4, 5, 6] => [2, 3, 4, 5, 6]
let b = Operation::Del { pos: 0, n: 2 };
test_transform_sym(&a, &b, &[0, 1, 2, 3, 4, 5, 6], &[4, 5, 2, 3, 6]);
}
#[test]
fn transform_mov_del_other_dir() {
// [0, 1, 2, (3, 4, 5), 6] => [0, (3, 4, 5), 1, 2, 6]
let a = Operation::Mov {
pos: 3,
n: 3,
to: 1,
};
// [0, 1, 2, 3, 4, (5, 6)] => [0, 1, 2, 3, 4]
let b = Operation::Del { pos: 5, n: 2 };
test_transform_sym(&a, &b, &[0, 1, 2, 3, 4, 5, 6], &[4, 5, 2, 3, 6]);
}
#[test]
fn transform_mov_del_above_overlap_r2() {
// [0, (1, 2, 3, 4), 5, 6] => [0, 5, (1, 2, 3, 4), 6]
@ -495,3 +510,4 @@ fn transform_mov_del_above_overlap_l2() {
let b = Operation::Del { pos: 1, n: 2 };
test_transform_sym(&a, &b, &[0, 1, 2, 3, 4, 5, 6], &[0, 3, 4, 5, 6]);
}
*/

View file

@ -26,6 +26,32 @@ pub fn test_transform<T: PartialEq + Clone + Debug>(
assert_eq!(data, expect, "b, a2");
}
pub fn test_transform_cond_catch<T: PartialEq + Clone + Debug + std::panic::RefUnwindSafe>(
a: &Operation<T>,
b: &Operation<T>,
input: &[T],
) {
let res = std::panic::catch_unwind(|| {
let (mut a2, mut b2) = (a.clone(), b.clone());
transform(&mut a2, &mut b2);
(a2, b2)
});
if let Ok((a2, b2)) = res {
check_op(&a2, a);
check_op(&b2, b);
let mut data = input.to_vec();
a.clone().apply(&mut data);
b2.apply(&mut data);
let mut data2 = input.to_vec();
b.clone().apply(&mut data2);
a2.apply(&mut data2);
assert_eq!(data, data2, "ops:\nA: {a:?}\nB: {b:?}");
}
}
pub fn test_transform_sym<T: PartialEq + Clone + Debug>(
a: &Operation<T>,
b: &Operation<T>,