From 5bd7b7617b4764bb18c6c181a1e16186a9b6eb2f Mon Sep 17 00:00:00 2001 From: Song Zhou Date: Mon, 11 Apr 2022 14:15:57 +0800 Subject: [PATCH] myers dff algo --- Cargo.lock | 8 + Cargo.toml | 3 + crates/skw-myers-diff/Cargo.toml | 18 ++ crates/skw-myers-diff/src/bench.rs | 43 +++ crates/skw-myers-diff/src/lib.rs | 7 + crates/skw-myers-diff/src/myers.rs | 451 +++++++++++++++++++++++++++++ crates/skw-myers-diff/src/parse.rs | 165 +++++++++++ crates/skw-myers-diff/src/types.rs | 161 ++++++++++ 8 files changed, 856 insertions(+) create mode 100644 crates/skw-myers-diff/Cargo.toml create mode 100644 crates/skw-myers-diff/src/bench.rs create mode 100644 crates/skw-myers-diff/src/lib.rs create mode 100644 crates/skw-myers-diff/src/myers.rs create mode 100644 crates/skw-myers-diff/src/parse.rs create mode 100644 crates/skw-myers-diff/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 23da7b6..4c775bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6631,6 +6631,14 @@ dependencies = [ name = "skw-contract-sys" version = "0.1.0" +[[package]] +name = "skw-myers-diff" +version = "0.0.0" +dependencies = [ + "bencher", + "rand 0.8.5", +] + [[package]] name = "skw-vm-engine" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 2ca9275..888b03a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,9 @@ members = [ 'crates/skw-contract-sys', 'crates/skw-contract-sim', + # a Myers Diff algo impl + 'crates/skw-myers-diff', + # sgx enclave host 'crates/skw-sgx-ipfs', ] diff --git a/crates/skw-myers-diff/Cargo.toml b/crates/skw-myers-diff/Cargo.toml new file mode 100644 index 0000000..0fc4d51 --- /dev/null +++ b/crates/skw-myers-diff/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "skw-myers-diff" +version = "0.0.0" +license = "GPL-3.0" +authors = ["SkyeKiwi ", "Near Inc "] +publish = false +# Please update rust-toolchain.toml as well when changing version here: +rust-version = "1.56.0" +edition = "2021" + +[dev-dependencies] +bencher = "0.1" +rand = "0.8.5" + +[[bench]] +name = "diff_patch" +path = "src/bench.rs" +harness = false \ No newline at end of file diff --git a/crates/skw-myers-diff/src/bench.rs b/crates/skw-myers-diff/src/bench.rs new file mode 100644 index 0000000..af71c36 --- /dev/null +++ b/crates/skw-myers-diff/src/bench.rs @@ -0,0 +1,43 @@ +#[macro_use] +extern crate bencher; + +use bencher::{Bencher}; + +use skw_myers_diff::{diff, patch, diff_ops_to_bytes, bytes_to_diff_ops}; + +macro_rules! random_bytes{ + ($len:expr) => ({ + let mut bytes = [0_u8; $len]; + for byte in bytes.iter_mut() { + *byte = rand::random::(); + } + bytes + }) +} + +fn diff_n_patch(bench: &mut Bencher) { + let mut original = random_bytes!(1000); + let mut modified = random_bytes!(10000); + + bench.iter(|| { + let res = diff(&mut original[..], &mut modified[..]); + let r = patch(res, &original[..]); + + assert_eq!(r, modified); + }); +} + +fn parse_diff_op(bench: &mut Bencher) { + let mut original = random_bytes!(1000); + let mut modified = random_bytes!(100000); + let res = diff(&mut original[..], &mut modified[..]); + + bench.iter(|| { + let bytes = diff_ops_to_bytes(res.clone()); + let r = bytes_to_diff_ops(&bytes[..]); + assert_eq!(r, res); + }); +} + +benchmark_group!(benches, parse_diff_op, diff_n_patch); +benchmark_main!(benches); \ No newline at end of file diff --git a/crates/skw-myers-diff/src/lib.rs b/crates/skw-myers-diff/src/lib.rs new file mode 100644 index 0000000..0ff656d --- /dev/null +++ b/crates/skw-myers-diff/src/lib.rs @@ -0,0 +1,7 @@ +mod types; +mod parse; +mod myers; + +pub use types::{DiffOp}; +pub use parse::{diff_ops_to_bytes, bytes_to_diff_ops}; +pub use myers::{diff, patch}; diff --git a/crates/skw-myers-diff/src/myers.rs b/crates/skw-myers-diff/src/myers.rs new file mode 100644 index 0000000..6642cf3 --- /dev/null +++ b/crates/skw-myers-diff/src/myers.rs @@ -0,0 +1,451 @@ +use crate::types::{DiffOp, WrappedBytes, ConsolidatedDiffOp, V}; + +#[allow(dead_code)] +#[derive(Debug)] +struct Snake { + x_start: usize, + y_start: usize, + x_end: usize, + y_end: usize, +} + +fn max_d(len1: usize, len2: usize) -> usize { + // XXX look into reducing the need to have the additional '+ 1' + (len1 + len2 + 1) / 2 + 1 +} + +// The divide part of a divide-and-conquer strategy. A D-path has D+1 snakes some of which may +// be empty. The divide step requires finding the ceil(D/2) + 1 or middle snake of an optimal +// D-path. The idea for doing so is to simultaneously run the basic algorithm in both the +// forward and reverse directions until furthest reaching forward and reverse paths starting at +// opposing corners 'overlap'. +fn find_middle_snake( + old: WrappedBytes, + new: WrappedBytes, + vf: &mut V, + vb: &mut V, +) -> (isize, Snake) { + let n = old.len(); + let m = new.len(); + + // By Lemma 1 in the paper, the optimal edit script length is odd or even as `delta` is odd + // or even. + let delta = n as isize - m as isize; + let odd = delta & 1 == 1; + + // The initial point at (0, -1) + vf[1] = 0; + // The initial point at (N, M+1) + vb[1] = 0; + + // We only need to explore ceil(D/2) + 1 + let d_max = max_d(n, m); + assert!(vf.len() >= d_max); + assert!(vb.len() >= d_max); + + for d in 0..d_max as isize { + // Forward path + for k in (-d..=d).rev().step_by(2) { + let mut x = if k == -d || (k != d && vf[k - 1] < vf[k + 1]) { + vf[k + 1] + } else { + vf[k - 1] + 1 + }; + let mut y = (x as isize - k) as usize; + + // The coordinate of the start of a snake + let (x0, y0) = (x, y); + // While these sequences are identical, keep moving through the graph with no cost + if let (Some(s1), Some(s2)) = (old.get(x..), new.get(y..)) { + let advance = common_prefix_len(s1, s2); + x += advance; + y += advance; + } + + // This is the new best x value + vf[k] = x; + // Only check for connections from the forward search when N - M is odd + // and when there is a reciprocal k line coming from the other direction. + if odd && (k - delta).abs() <= (d - 1) { + // TODO optimize this so we don't have to compare against n + if vf[k] + vb[-(k - delta)] >= n { + // Return the snake + let snake = Snake { + x_start: x0, + y_start: y0, + x_end: x, + y_end: y, + }; + // Edit distance to this snake is `2 * d - 1` + return (2 * d - 1, snake); + } + } + } + + // Backward path + for k in (-d..=d).rev().step_by(2) { + let mut x = if k == -d || (k != d && vb[k - 1] < vb[k + 1]) { + vb[k + 1] + } else { + vb[k - 1] + 1 + }; + let mut y = (x as isize - k) as usize; + + // The coordinate of the start of a snake + let (x0, y0) = (x, y); + if x < n && y < m { + let advance = common_suffix_len(old.slice(..n - x), new.slice(..m - y)); + x += advance; + y += advance; + } + + // This is the new best x value + vb[k] = x; + + if !odd && (k - delta).abs() <= d { + // TODO optimize this so we don't have to compare against n + if vb[k] + vf[-(k - delta)] >= n { + // Return the snake + let snake = Snake { + x_start: n - x, + y_start: m - y, + x_end: n - x0, + y_end: m - y0, + }; + // Edit distance to this snake is `2 * d` + return (2 * d, snake); + } + } + } + + // TODO: Maybe there's an opportunity to optimize and bail early? + } + + unreachable!("unable to find a middle snake"); +} + +fn common_prefix_len(a: WrappedBytes, b: WrappedBytes) -> usize { + let a_len = a.len(); + let b_len = b.len(); + + if a_len == 0 || b_len == 0 { + return 0; + } + + if a.inner_get(0..1) != b.inner_get(0..1) { + return 0; + } + + let mut t = 0; + while t < a_len && t < b_len && a.inner_get(t..t + 1) == b.inner_get(t..t + 1) { + t += 1; + } + t + // let mut m = 0; + // let mut ma = std::cmp::min(a.len(), b.len()); + // let mut mid = ma; + // let mut start = 0; + + // while m < mid { + // if a.inner_get(start .. m) == b.inner_get(start .. m) { + // m = mid; start = m; + // } else { + // ma = mid; + // } + // mid = (ma - m) / 2 + m; + // } + + // mid +} + +fn common_suffix_len(a: WrappedBytes, b: WrappedBytes) -> usize { + let a_len = a.len(); + let b_len = b.len(); + + + if a_len == 0 || b_len == 0 { + return 0; + } + + if a.inner_get(a_len - 1..a_len) != b.inner_get(b_len - 1..b_len) { + return 0; + } + + let mut m = 0; + let mut ma = std::cmp::min(a_len, b_len); + let mut mid = ma; + let mut end = 0; + + while m < mid { + if a.inner_get(a_len - mid..a_len - end) == b.inner_get(b_len - mid..b_len - end) { + m = mid; end = m; + } else { + ma = mid; + } + mid = (ma - m) / 2 + m; + } + + mid +} + +fn conquer<'a>( + mut old: WrappedBytes<'a>, + mut new: WrappedBytes<'a>, + vf: &mut V, + vb: &mut V, + solution: &mut Vec>, +) { + // Check for common prefix + let common_prefix_len = common_prefix_len(old, new); + if common_prefix_len > 0 { + + let common_prefix = DiffOp::Equal( + old.slice(..common_prefix_len), + new.slice(..common_prefix_len), + ); + solution.push(common_prefix); + } + old = old.slice(common_prefix_len..old.len()); + new = new.slice(common_prefix_len..new.len()); + + // Check for common suffix + let common_suffix_len = common_suffix_len(old, new); + let common_suffix = DiffOp::Equal( + old.slice(old.len() - common_suffix_len..old.len()), + new.slice(new.len() - common_suffix_len..new.len()), + ); + + old = old.slice(..old.len() - common_suffix_len); + new = new.slice(..new.len() - common_suffix_len); + + if old.is_empty() && new.is_empty() { + // Do nothing + } else if old.is_empty() { + // Inserts + solution.push(DiffOp::Insert(new)); + } else if new.is_empty() { + // Deletes + solution.push(DiffOp::Delete(old)); + } else { + // Divide & Conquer + let (_shortest_edit_script_len, snake) = find_middle_snake(old, new, vf, vb); + let (old_a, old_b) = old.split_at(snake.x_start); + let (new_a, new_b) = new.split_at(snake.y_start); + + conquer(old_a, new_a, vf, vb, solution); + conquer(old_b, new_b, vf, vb, solution); + } + + if common_suffix_len > 0 { + solution.push(common_suffix); + } +} + +pub fn diff<'a>(old: &'a [u8], new: &'a [u8]) -> Vec { + + let wrapped_old = WrappedBytes::new(old, ..); + let wrapped_new = WrappedBytes::new(new, ..); + + let mut solution = Vec::new(); + + // The arrays that hold the 'best possible x values' in search from: + // `vf`: top left to bottom right + // `vb`: bottom right to top left + let max_d = max_d(old.len(), new.len()); + let mut vf = V::new(max_d); + let mut vb = V::new(max_d); + + let mut consolidated_solution = Vec::new(); + conquer(wrapped_old, wrapped_new, &mut vf, &mut vb, &mut solution); + merge_diff_ops(solution, &mut consolidated_solution); + + consolidated_solution +} + +pub fn merge_diff_ops<'a>(solution: Vec>, result: &mut Vec) { + for op in solution { + match op { + DiffOp::Equal(a, _b) => { + result.push(ConsolidatedDiffOp::Equal(a.offset(), a.len())); + }, + DiffOp::Insert(a) => { + result.push(ConsolidatedDiffOp::Insert(a.clone().dump())); + }, + DiffOp::Delete(a) => { + result.push(ConsolidatedDiffOp::Delete(a.offset(), a.len())); + }, + } + } + + let old_result = result.clone(); + * result = Vec::new(); + let mut p1 = 0; + let mut p2 = 1; + let old_result_len = old_result.len(); + + while p1 < old_result_len { + let op1 = old_result[p1].clone(); + + match op1 { + ConsolidatedDiffOp::Equal(offset1, len1) => { + let offset = offset1; + let mut len = len1; + + while p2 < old_result_len { + let op2 = old_result[p2].clone(); + match op2 { + ConsolidatedDiffOp::Equal(offset2, len2) => { + if offset2 == offset + len { + len += len2; + p2 += 1; + } else { + println!("Unexpected"); + break; + } + }, + _ => { + break; + } + } + } + + result.push(ConsolidatedDiffOp::Equal(offset, len)); + }, + ConsolidatedDiffOp::Insert(v1) => { + let mut buf = v1.clone(); + + while p2 < old_result_len { + let op2 = old_result[p2].clone(); + match op2 { + ConsolidatedDiffOp::Insert(v2) => { + buf.extend_from_slice(&v2[..]); + p2 += 1; + }, + _ => { + break; + } + } + } + + result.push(ConsolidatedDiffOp::Insert(buf)); + }, + ConsolidatedDiffOp::Delete(offset1, len1) => { + let offset = offset1; + let mut len = len1; + + while p2 < old_result_len { + let op2 = old_result[p2].clone(); + match op2 { + ConsolidatedDiffOp::Delete(offset2, len2) => { + if offset2 == offset + len { + len += len2; + p2 += 1; + } else { + break; + } + }, + _ => { + break; + } + } + } + + result.push(ConsolidatedDiffOp::Delete(offset, len)); + }, + } + p1 = p2; + p2 += 1; + } +} + +pub fn patch(patch: Vec, origin: &[u8]) -> Vec { + let mut new = Vec::new(); + + for op in patch { + match op { + ConsolidatedDiffOp::Equal(offset, len) => { + new.extend_from_slice(&origin.get(offset..offset + len).unwrap()[..]); + } + ConsolidatedDiffOp::Insert(a) => { + new.extend_from_slice(&a[..]); + } + ConsolidatedDiffOp::Delete(_a, _b) => { + // + } + } + } + + new +} + +#[test] +fn test_diff() { + macro_rules! random_bytes{ + ($len:expr) => ({ + let mut bytes = [0_u8; $len]; + for byte in bytes.iter_mut() { + *byte = rand::random::(); + } + bytes + }) + } + + // delete more bytes than insert + let mut loops = 3; + while loops > 0 { + let mut old = random_bytes!(1000); + let mut new = random_bytes!(100); + + let res = diff(&mut old[..], &mut new[..]); + let recovered = patch(res, &old[..]); + assert_eq!(recovered, new); + + loops -= 1; + } + + loops = 3; + while loops > 0 { + let mut old = random_bytes!(1000); + let mut new = random_bytes!(1000); + + let res = diff(&mut old[..], &mut new[..]); + let recovered = patch(res, &old[..]); + assert_eq!(recovered, new); + + loops -= 1; + } + + loops = 3; + while loops > 0 { + let mut old = random_bytes!(100); + let mut new = random_bytes!(10000); + + let res = diff(&mut old[..], &mut new[..]); + let recovered = patch(res, &old[..]); + assert_eq!(recovered, new); + + loops -= 1; + } +} + +#[test] +fn test_common_prefix_length() { + let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let b = [1, 2, 3, 4, 5, 6,]; + + let wrapped_a = WrappedBytes::new(&a[..], ..); + let wrapped_b = WrappedBytes::new(&b[..], ..); + + assert_eq!(common_prefix_len(wrapped_a, wrapped_b), 6); +} + +#[test] +fn test_common_suffix_length() { + let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let b = [6, 7, 8, 9, 10]; + + let wrapped_a = WrappedBytes::new(&a[..], ..); + let wrapped_b = WrappedBytes::new(&b[..], ..); + + assert_eq!(common_suffix_len(wrapped_a, wrapped_b), 5); +} \ No newline at end of file diff --git a/crates/skw-myers-diff/src/parse.rs b/crates/skw-myers-diff/src/parse.rs new file mode 100644 index 0000000..b4a2869 --- /dev/null +++ b/crates/skw-myers-diff/src/parse.rs @@ -0,0 +1,165 @@ +use crate::types::{ConsolidatedDiffOp}; + +fn pad_size(size: usize) -> [u8; 4] { + let mut v = [0, 0, 0, 0]; + v[3] = (size & 0xff) as u8; + v[2] = ((size >> 8) & 0xff) as u8; + v[1] = ((size >> 16) & 0xff) as u8; + v[0] = ((size >> 24) & 0xff) as u8; + v +} + +fn unpad_size(size: &[u8; 4]) -> usize { + if size.len() != 4 { + panic!("Invalid size"); + } + return ( + size[3] as usize | + ((size[2] as usize) << 8) | + ((size[1] as usize) << 16) | + ((size[0] as usize) << 24) + ).into(); +} + +pub fn diff_ops_to_bytes(ops: Vec) -> Vec { + let mut v = Vec::new(); + for op in ops { + match op { + ConsolidatedDiffOp::Equal(offset, len) => { + let mut v_op = Vec::new(); + v_op.push(0); + v_op.extend_from_slice(&pad_size(offset)); + v_op.extend_from_slice(&pad_size(len)); + v.extend_from_slice(&v_op); + }, + ConsolidatedDiffOp::Insert(ins) => { + let mut v_op = Vec::new(); + v_op.push(1); + v_op.extend_from_slice(&pad_size(ins.len())); + v_op.extend_from_slice(&ins[..]); + v.extend_from_slice(&v_op); + }, + ConsolidatedDiffOp::Delete(offset, len) => { + let mut v_op = Vec::new(); + v_op.push(2); + v_op.extend_from_slice(&pad_size(offset)); + v_op.extend_from_slice(&pad_size(len)); + v.extend_from_slice(&v_op); + }, + } + } + v +} + +pub fn bytes_to_diff_ops(bytes: &[u8]) -> Vec { + let mut ops = Vec::new(); + let mut i = 0; + while i < bytes.len() { + let op_type = bytes[i]; + i += 1; + + match op_type { + 0 => { + ops.push(ConsolidatedDiffOp::Equal( + unpad_size(&bytes[i..i + 4].try_into().expect("Invalid size")), + unpad_size(&bytes[i + 4..i + 8].try_into().expect("Invalid size")) + )); + i += 8; + }, + 1 => { + let a = unpad_size(&bytes[i..i + 4].try_into().expect("Invalid size")); + i += 4; + let v = &bytes[i..i + a]; + ops.push(ConsolidatedDiffOp::Insert(v.to_vec())); + i += a; + }, + 2 => { + ops.push(ConsolidatedDiffOp::Delete( + unpad_size(&bytes[i..i + 4].try_into().expect("Invalid size")), + unpad_size(&bytes[i + 4..i + 8].try_into().expect("Invalid size")) + )); + i += 8; + }, + _ => { + panic!("Invalid op type"); + }, + } + } + ops +} + + +#[cfg(test)] +use crate::{diff, patch}; + +#[test] +fn test_pad_size() { + assert_eq!(pad_size(0), [0, 0, 0, 0]); + assert_eq!(pad_size(1), [0, 0, 0, 1]); + assert_eq!(pad_size(511), [0, 0, 1, 255]); + assert_eq!(pad_size(65_536), [0, 1, 0, 0]); + assert_eq!(pad_size(131_071), [0, 1, 255, 255]); + assert_eq!(pad_size(16_777_216), [1, 0, 0, 0]); + assert_eq!(pad_size(4_278_190_080), [255, 0, 0, 0]); +} + + + +#[test] +fn test_unpad_size() { + assert_eq!(unpad_size(&[0, 0, 0, 0]), 0); + assert_eq!(unpad_size(&[0, 0, 0, 1]), 1); + assert_eq!(unpad_size(&[0, 0, 1, 255]), 511); + assert_eq!(unpad_size(&[0, 1, 0, 0]), 65_536); + assert_eq!(unpad_size(&[0, 1, 255, 255]), 131_071); + assert_eq!(unpad_size(&[1, 0, 0, 0]), 16_777_216); + assert_eq!(unpad_size(&[255, 0, 0, 0]), 4_278_190_080); +} + +#[test] +fn test_diff_ops_to_bytes() { + let ops = vec![ + ConsolidatedDiffOp::Equal(0, 1), + ConsolidatedDiffOp::Insert(vec![1, 2, 3]), + ConsolidatedDiffOp::Delete(0, 1), + ]; + let bytes = diff_ops_to_bytes(ops); + let ops = bytes_to_diff_ops(&bytes); + assert_eq!(ops, vec![ + ConsolidatedDiffOp::Equal(0, 1), + ConsolidatedDiffOp::Insert(vec![1, 2, 3]), + ConsolidatedDiffOp::Delete(0, 1), + ]); +} + +#[test] +fn test_e2e() { + macro_rules! random_bytes{ + ($len:expr) => ({ + let mut bytes = [0_u8; $len]; + for byte in bytes.iter_mut() { + *byte = rand::random::(); + } + bytes + }) + } + + let mut loops = 1; + + while loops > 0 { + let mut old = random_bytes!(1000); + let mut new = random_bytes!(1000); + + let res = diff(&mut old[..], &mut new[..]); + + let bytes = diff_ops_to_bytes(res); + + println!("{:?}", bytes.len()); + let r = bytes_to_diff_ops(&bytes); + + let recovered = patch(r, &old[..]); + assert_eq!(recovered, new); + + loops -= 1; + } +} \ No newline at end of file diff --git a/crates/skw-myers-diff/src/types.rs b/crates/skw-myers-diff/src/types.rs new file mode 100644 index 0000000..e5666be --- /dev/null +++ b/crates/skw-myers-diff/src/types.rs @@ -0,0 +1,161 @@ +use std::{fmt::Debug}; +use std::ops::{Index, IndexMut}; + +#[derive(Debug, PartialEq)] +pub enum DiffOp<'a> { + Equal(WrappedBytes<'a>, WrappedBytes<'a>), + Insert(WrappedBytes<'a>), + Delete(WrappedBytes<'a>), +} + +#[derive(Debug, PartialEq, Clone)] +pub enum ConsolidatedDiffOp { + Equal(usize, usize), + Insert(Vec), + Delete(usize, usize), +} + +#[derive(Debug, PartialEq)] +pub struct WrappedBytes<'a> { + inner: &'a [u8], + offset: usize, + len: usize, +} + +impl Copy for WrappedBytes<'_> {} + +impl Clone for WrappedBytes<'_> { + fn clone(&self) -> Self { + *self + } +} + +impl <'a> WrappedBytes <'a> { + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn new(inner: &'a [u8], bounds: impl RangeBounds) -> Self { + let (offset, len) = bounds.index(inner.len()); + WrappedBytes { inner, offset, len, } + } + + pub fn slice(&self, bounds: impl RangeBounds) -> Self { + let (offset, len) = bounds.index(self.len); + WrappedBytes { + inner: self.inner, + offset: self.offset + offset, len + } + } + + pub fn split_at(&self, mid: usize) -> (Self, Self) { + (self.slice(..mid), self.slice(mid..)) + } + + pub fn get(&self, bounds: impl RangeBounds) -> Option { + let (offset, len) = bounds.try_index(self.len)?; + Some(WrappedBytes { + inner: self.inner, + offset: self.offset + offset, len + }) + } + + pub fn inner_get(&self, bounds: impl RangeBounds) -> Vec { + let (offset, len) = bounds.try_index(self.len).expect("wrong range"); + self.inner[self.offset + offset..self.offset + offset + len].to_vec() + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn offset(&self) -> usize { + self.offset + } + + pub fn dump(&self) -> Vec { + self.inner[self.offset..self.offset + self.len].to_vec() + } +} + +pub trait RangeBounds: Sized + Clone + Debug { + // Returns (offset, len). + fn try_index(self, len: usize) -> Option<(usize, usize)>; + + fn index(self, len: usize) -> (usize, usize) { + match self.clone().try_index(len) { + Some(range) => range, + None => panic!("index out of range, index={:?}, len={}", self, len), + } + } +} + +impl RangeBounds for std::ops::Range { + fn try_index(self, len: usize) -> Option<(usize, usize)> { + if self.start <= self.end && self.end <= len { + Some((self.start, self.end - self.start)) + } else { + None + } + } +} + +impl RangeBounds for std::ops::RangeFrom { + fn try_index(self, len: usize) -> Option<(usize, usize)> { + if self.start <= len { + Some((self.start, len - self.start)) + } else { + None + } + } +} + +impl RangeBounds for std::ops::RangeTo { + fn try_index(self, len: usize) -> Option<(usize, usize)> { + if self.end <= len { + Some((0, self.end)) + } else { + None + } + } +} + +impl RangeBounds for std::ops::RangeFull { + fn try_index(self, len: usize) -> Option<(usize, usize)> { + Some((0, len)) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct V { + offset: isize, + v: Vec, // Look into initializing this to -1 and storing isize +} + +impl V { + pub(crate) fn new(max_d: usize) -> Self { + Self { + offset: max_d as isize, + v: vec![0; 2 * max_d], + } + } + + pub(crate) fn len(&self) -> usize { + self.v.len() + } +} + +impl Index for V { + type Output = usize; + + fn index(&self, index: isize) -> &Self::Output { + &self.v[(index + self.offset) as usize] + } +} + +impl IndexMut for V { + fn index_mut(&mut self, index: isize) -> &mut Self::Output { + &mut self.v[(index + self.offset) as usize] + } +} \ No newline at end of file