diff --git a/backend/reconcile/src/utils/common_prefix_len.rs b/backend/reconcile/src/utils/common_prefix_len.rs new file mode 100644 index 0000000..08da6bd --- /dev/null +++ b/backend/reconcile/src/utils/common_prefix_len.rs @@ -0,0 +1,46 @@ +use std::ops::{Index, Range}; + +/// Given two lookups and ranges calculates the length of the common prefix. +/// Copied from https://github.com/mitsuhiko/similar/blob/7e15c44de11a1cd61e1149189929e189ef977fd8/src/algorithms/utils.rs +pub fn common_prefix_len( + old: &Old, + old_range: Range, + new: &New, + new_range: Range, +) -> usize +where + Old: Index + ?Sized, + New: Index + ?Sized, + New::Output: PartialEq, +{ + new_range + .zip(old_range) + .take_while(|x| new[x.0] == old[x.1]) + .count() +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_common_prefix_len() { + assert_eq!( + common_prefix_len("".as_bytes(), 0..0, "".as_bytes(), 0..0), + 0 + ); + assert_eq!( + common_prefix_len("foobarbaz".as_bytes(), 0..9, "foobarblah".as_bytes(), 0..10), + 7 + ); + assert_eq!( + common_prefix_len("foobarbaz".as_bytes(), 0..9, "blablabla".as_bytes(), 0..9), + 0 + ); + assert_eq!( + common_prefix_len("foobarbaz".as_bytes(), 3..9, "foobarblah".as_bytes(), 3..10), + 4 + ); + } +} diff --git a/backend/reconcile/src/utils/common_suffix_len.rs b/backend/reconcile/src/utils/common_suffix_len.rs new file mode 100644 index 0000000..ab4bc5e --- /dev/null +++ b/backend/reconcile/src/utils/common_suffix_len.rs @@ -0,0 +1,47 @@ +use std::ops::{Index, Range}; + +/// Given two lookups and ranges calculates the length of common suffix. +/// Copied from https://github.com/mitsuhiko/similar/blob/7e15c44de11a1cd61e1149189929e189ef977fd8/src/algorithms/utils.rs +pub fn common_suffix_len( + old: &Old, + old_range: Range, + new: &New, + new_range: Range, +) -> usize +where + Old: Index + ?Sized, + New: Index + ?Sized, + New::Output: PartialEq, +{ + new_range + .rev() + .zip(old_range.rev()) + .take_while(|x| new[x.0] == old[x.1]) + .count() +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_common_suffix_len() { + assert_eq!( + common_suffix_len("".as_bytes(), 0..0, "".as_bytes(), 0..0), + 0 + ); + assert_eq!( + common_suffix_len("1234".as_bytes(), 0..4, "X0001234".as_bytes(), 0..8), + 4 + ); + assert_eq!( + common_suffix_len("1234".as_bytes(), 0..4, "Xxxx".as_bytes(), 0..4), + 0 + ); + assert_eq!( + common_suffix_len("1234".as_bytes(), 2..4, "01234".as_bytes(), 2..5), + 2 + ); + } +} diff --git a/backend/reconcile/src/utils/merge_iters.rs b/backend/reconcile/src/utils/merge_iters.rs new file mode 100644 index 0000000..6826615 --- /dev/null +++ b/backend/reconcile/src/utils/merge_iters.rs @@ -0,0 +1,87 @@ +use std::cmp::Ordering; +use std::iter::Peekable; + +pub struct MergeAscending +where + L: Iterator, + R: Iterator, + F: Fn(&R::Item) -> O, + O: PartialOrd, +{ + left: Peekable, + right: Peekable, + get_key: F, +} + +impl MergeAscending +where + L: Iterator, + R: Iterator, + F: Fn(&R::Item) -> O, + O: PartialOrd, +{ + fn new(left: L, right: R, get_key: F) -> Self { + MergeAscending { + left: left.peekable(), + right: right.peekable(), + get_key, + } + } +} + +impl Iterator for MergeAscending +where + L: Iterator, + R: Iterator, + F: Fn(&R::Item) -> O, + O: PartialOrd, +{ + type Item = L::Item; + + fn next(&mut self) -> Option { + let order = match (self.left.peek(), self.right.peek()) { + (Some(l), Some(r)) => (self.get_key)(l).partial_cmp(&(self.get_key)(r)), + (Some(_), None) => Some(Ordering::Less), + (None, Some(_)) => Some(Ordering::Greater), + (None, None) => return None, + }; + + match order { + Some(Ordering::Less) | None => self.left.next(), + Some(Ordering::Equal) => self.left.next(), + Some(Ordering::Greater) => self.right.next(), + } + } +} + +pub trait MergeSorted: Iterator { + fn merge_sorted_by_key(self, other: R, get_key: F) -> MergeAscending + where + Self: Sized, + R: Iterator, + F: Fn(&Self::Item) -> O, + O: PartialOrd, + { + MergeAscending::new(self, other, get_key) + } +} + +impl MergeSorted for T where T: Iterator {} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_merge_sorted_by_key() { + let left = [9, 7, 5, 3, 1]; + let right = [7, 6, 5, 4, 3]; + + let result: Vec = left + .into_iter() + .merge_sorted_by_key(right.into_iter(), |x| -1 * x) + .collect(); + assert_eq!(result, vec![9, 7, 7, 6, 5, 5, 4, 3, 3, 1]); + } +} diff --git a/backend/reconcile/src/utils/mod.rs b/backend/reconcile/src/utils/mod.rs new file mode 100644 index 0000000..d8a272b --- /dev/null +++ b/backend/reconcile/src/utils/mod.rs @@ -0,0 +1,5 @@ +pub mod common_prefix_len; +pub mod common_suffix_len; +pub mod merge_iters; +pub mod ordered_operation; +pub mod side; diff --git a/backend/reconcile/src/utils/ordered_operation.rs b/backend/reconcile/src/utils/ordered_operation.rs new file mode 100644 index 0000000..065853f --- /dev/null +++ b/backend/reconcile/src/utils/ordered_operation.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::operations::Operation; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct OrderedOperation { + pub order: usize, + pub operation: Operation, +} diff --git a/backend/reconcile/src/utils/side.rs b/backend/reconcile/src/utils/side.rs new file mode 100644 index 0000000..bfeee2c --- /dev/null +++ b/backend/reconcile/src/utils/side.rs @@ -0,0 +1,4 @@ +pub enum Side { + Left, + Right, +}