diff --git a/src/experimental/zipper_algebra.rs b/src/experimental/zipper_algebra.rs index 13181e1..cb10802 100644 --- a/src/experimental/zipper_algebra.rs +++ b/src/experimental/zipper_algebra.rs @@ -29,7 +29,7 @@ pub use zipper_algebra_poly::ZipperMergeF; /// without visiting unrelated regions. /// /// Each method delegates to a corresponding free function ([`zipper_join`], -/// [`zipper_meet`], [`zipper_subtract`]), preserving their performance +/// [`zipper_meet`], [`zipper_subtract`], [`zipper_xor`]), preserving their performance /// characteristics and semantics. /// /// # Semantics @@ -39,6 +39,7 @@ pub use zipper_algebra_poly::ZipperMergeF; /// - [`join`](Self::join): least upper bound (union-like merge), /// - [`meet`](Self::meet): greatest lower bound (intersection), /// - [`subtract`](Self::subtract): asymmetric difference (`lhs \ rhs`). +/// - [`xor`](Self::xor): symmetric difference (`(lhs \/ rhs) \ (lhs /\ rhs)`). /// /// All operations write their result into a separate output zipper implementing /// [`ZipperWriting`]. @@ -56,6 +57,7 @@ pub use zipper_algebra_poly::ZipperMergeF; /// - [`zipper_join`] /// - [`zipper_meet`] /// - [`zipper_subtract`] +/// - [`zipper_xor`] pub trait ZipperAlgebraExt: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving + Sized { @@ -88,6 +90,16 @@ pub trait ZipperAlgebraExt: { zipper_subtract(self, rhs, out); } + + #[inline] + fn xor(&mut self, rhs: &mut ZR, out: &mut Out) + where + V: DistributiveLattice + Lattice, + ZR: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, + Out: ZipperWriting, + { + zipper_xor(self, rhs, out); + } } impl ZipperAlgebraExt @@ -327,6 +339,90 @@ pub fn zipper_subtract3( zipper_merge3::(lhs, mid, rhs, out); } +/// Performs a symmetric difference of two radix-256 tries using zipper traversal. +/// +/// This function computes the symmetric difference (`XOR`) of two tries whose +/// values form a distributive lattice. The traversal strategy is identical to +/// [`zipper_join`]: +/// +/// - Child edges are treated as sorted sequences and merged lexicographically. +/// - Subtries that exist only on one side are grafted directly into the output. +/// - The traversal descends only into child edges present in both inputs. +/// - Descent is implemented iteratively via zipper movement and an explicit +/// depth counter. +/// +/// # Value semantics +/// +/// When both tries contain a value at the same key, the values are combined +/// using the lattice symmetric-difference operation. +/// +/// For distributive lattices, symmetric difference is defined as: +/// +/// ```text +/// (a ∨ b) \ (a ∧ b) +/// ``` +/// +/// Equivalently: +/// +/// ```text +/// (a \ b) ∨ (b \ a) +/// ``` +/// +/// If the resulting value is the lattice bottom element, no value is written +/// to the output. +/// +/// Values that occur in only one input trie are propagated unchanged. +/// +/// # Complexity +/// +/// Let: +/// +/// - `h` be the maximum key length, +/// - `d` be the size of overlapping subtries, +/// - `f` be the number of distinct child edges encountered during traversal. +/// +/// Then: +/// +/// - Best case (disjoint tries): `O(h)` +/// - Typical case: `O(h + f)` +/// - Worst case (identical structure): `O(n)` +/// +/// Entire disjoint subtries are copied without traversal whenever possible. +/// +/// # Notes +/// +/// The traversal logic is identical to [`zipper_join`]. Only the value +/// combination operation differs. +pub fn zipper_xor(lhs: &mut ZL, rhs: &mut ZR, out: &mut Out) +where + V: DistributiveLattice + Lattice + Clone + Send + Sync, + A: Allocator, + ZL: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, + ZR: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, + Out: ZipperWriting, +{ + zipper_merge::(lhs, rhs, out); +} + +/// Performs a symmetric difference (XOR) of three radix-256 tries using zipper traversal. +/// That is, it performs: (`lhs` △ `mid`) △ `rhs`, where `△` = [`zipper_xor`] +/// +/// # See also +/// +/// [`zipper_xor`] +/// +pub fn zipper_xor3(lhs: &mut ZL, mid: &mut ZM, rhs: &mut ZR, out: &mut Out) +where + V: DistributiveLattice + Lattice + Clone + Send + Sync, + A: Allocator, + ZL: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, + ZM: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, + ZR: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, + Out: ZipperWriting, +{ + zipper_merge3::(lhs, mid, rhs, out); +} + trait MergePolicy { #[inline] fn on_left_only(z: &mut Z, range: ByteMask, out: &mut Out) @@ -355,7 +451,8 @@ trait MergePolicy { Out: ZipperWriting; fn descend_on_some_equal(mask: u64) -> bool; - fn on_id(z: &mut Z, out: &mut Out) + + fn on_id(z: &mut Z, n: usize, out: &mut Out) where A: Allocator, Z: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, @@ -427,7 +524,7 @@ where if let Some(v) = P::combine_n_times(lift(lhs.val()), 2) { out.set_val(v); } - P::on_id(lhs, out); + P::on_id(lhs, 2, out); return; } @@ -476,7 +573,7 @@ where if let Some(v) = P::combine_n_times(lift(lhs.val()), 2) { out.set_val(v); } - P::on_id(lhs, out); + P::on_id(lhs, 2, out); rhs.ascend_byte(); rhs_next = rhs_mask.next_bit(lhs_byte); @@ -604,7 +701,7 @@ where if let Some(v) = P::combine_n_times(lift(lhs.val()), 3) { out.set_val(v); } - P::on_id(lhs, out); + P::on_id(lhs, 3, out); return; } @@ -709,7 +806,7 @@ where if let Some(v) = P::combine_n_times(lift(lhs.val()), 3) { out.set_val(v); } - P::on_id(lhs, out); + P::on_id(lhs, 3, out); rhs.ascend_byte(); r = rhs_mask.next_bit(min); @@ -811,7 +908,7 @@ fn zipper_merge4( if let Some(v) = P::combine_n_times(lift(z0.val()), 4) { out.set_val(v); } - P::on_id(z0, out); + P::on_id(z0, 4, out); return; } @@ -873,7 +970,7 @@ fn zipper_merge4( if let Some(v) = P::combine_n_times(lift(z0.val()), 4) { out.set_val(v); } - P::on_id(z0, out); + P::on_id(z0, 4, out); z3.ascend_byte(); b3 = m3.next_bit(min); @@ -1201,6 +1298,69 @@ where zipper_merge_n_mono::(zs, (1 << N) - 1, out); } +/// Performs an n-way symmetric difference of radix-256 tries using zipper traversal. +/// +/// The input tries are interpreted as sparse mappings from paths to values, +/// where missing paths implicitly contain the lattice bottom element. +/// +/// For each path `p`, the resulting value is computed as the n-ary symmetric +/// difference of all values present at `p`: +/// +/// `text +/// result(p) = xor(v₁(p), v₂(p), ..., vₙ(p)) +/// ` +/// +/// where absent values are treated as bottom. +/// +/// Operationally, the traversal behaves similarly to an n-way join: +/// +/// - A child edge is traversed whenever it is present in at least one input. +/// - Subtries that appear in only one input are grafted directly into the +/// output. +/// - Values at coincident paths are combined using the lattice symmetric +/// difference operation. +/// - Paths whose resulting value is bottom are omitted from the output. +/// +/// # Why traversal follows join semantics +/// +/// Although symmetric difference is often associated with parity ("a path +/// survives iff it appears an odd number of times"), this implementation +/// performs XOR on values rather than on path presence. +/// +/// This distinction is important because cancellation at a path does not imply +/// cancellation of its descendants. A node whose value XORs to bottom may still +/// contain descendant paths whose values survive. +/// +/// Consequently, traversal proceeds whenever any input contains a child edge, +/// just as in join. Pruning is only valid when an entire subtrie is known to +/// cancel. +/// +/// # Complexity +/// +/// Let: +/// +/// - `h` be the maximum key length, +/// - `f` be the number of frontier edges visited during traversal, +/// - `d` be the size of overlapping subtries. +/// +/// Then: +/// +/// - Best case (disjoint tries): `O(h)` +/// - Typical case: `O(h + f)` +/// - Worst case: `O(n)` +/// +/// As with join, large disjoint subtries are copied without traversal whenever +/// possible. +pub fn zipper_n_xor(zs: &mut [Z; N], out: &mut Out) +where + V: Lattice + DistributiveLattice + Clone + Send + Sync + Unpin, + Z: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, + Out: ZipperWriting, + A: Allocator, +{ + zipper_merge_n_mono::(zs, (1 << N) - 1, out); +} + // small micro-helpers #[inline(always)] fn for_each_bit(mut bits: u64, mut f: impl FnMut(usize)) { @@ -1211,16 +1371,37 @@ fn for_each_bit(mut bits: u64, mut f: impl FnMut(usize)) { } } -#[inline] -fn active_bits(active: u64) -> impl Iterator { - (0..N).filter(move |i| (active >> i) & 1 != 0) +struct ActiveRefs<'a, T, const N: usize> { + bits: u64, + xs: &'a [T; N], +} + +impl<'a, T, const N: usize> Iterator for ActiveRefs<'a, T, N> { + type Item = &'a T; + + #[inline(always)] + fn next(&mut self) -> Option { + if self.bits == 0 { + return None; + } + + let i = self.bits.trailing_zeros() as usize; + self.bits &= self.bits - 1; + + Some(&self.xs[i]) + } } -fn only_active<'a, T, const N: usize>( - ts: &'a [T; N], - active: u64, -) -> impl Iterator { - active_bits::(active).map(|i| (i, &ts[i])) +fn active_refs(xs: &[T; N], bits: u64) -> ActiveRefs { + assert!(bits >> N == 0); + ActiveRefs { bits, xs } +} + +#[inline(always)] +fn first_active(ts: &[T; N], active: u64) -> &T { + debug_assert_ne!(active, 0); + let i0 = active.trailing_zeros() as usize; + &ts[i0] } #[inline(always)] @@ -1230,6 +1411,35 @@ fn first_active_mut(ts: &mut [T; N], active: u64) -> &mut T { &mut ts[i0] } +#[inline(always)] +fn with_k( + xs: &mut [T], + mut bits: u64, + f: impl FnOnce([&mut T; K]) -> R, +) -> R { + debug_assert!(bits.count_ones() as usize >= K); + + // collect raw pointers first (safe) + let mut ptrs: [*mut T; K] = [std::ptr::null_mut(); K]; + + let mut i = 0; + while i < K { + let idx = bits.trailing_zeros() as usize; + bits &= bits - 1; + ptrs[i] = unsafe { xs.as_mut_ptr().add(idx) }; + i += 1; + } + + // SAFETY: + // - indices are distinct (bitmask) + // - derived from same slice + + // should be zero-cost after inlining + let refs = unsafe { ptrs.map(|p| &mut *p) }; + + f(refs) +} + // - The function is fully monomorphized over `Z` and `N` and uses a bitmask (`active`) // to track participating zippers. // - Small frontiers (`k ≤ 4`) are dispatched to specialized implementations @@ -1256,56 +1466,28 @@ where V: Clone + 'a, Z: ZipperValues, { - only_active(zs, active).map(|(_, z)| lift(z.val())) + active_refs(zs, active).map(|z| lift(z.val())) } fn all_active_share(zs: &[Z; N], active: u64) -> bool where Z: ZipperConcrete, { - let mut iter = only_active(zs, active).map(|(_, z)| z.shared_node_id()); + let mut iter = active_refs(zs, active).map(|z| z.shared_node_id()); match iter.next() { Some(Some(first)) => iter.all(|next| next.is_some_and(|snid| snid == first)), _ => false, } } - #[inline(always)] - fn with_k( - xs: &mut [T], - mut bits: u64, - f: impl FnOnce([&mut T; K]) -> R, - ) -> R { - debug_assert!(bits.count_ones() as usize >= K); - - // collect raw pointers first (safe) - let mut ptrs: [*mut T; K] = [std::ptr::null_mut(); K]; - - let mut i = 0; - while i < K { - let idx = bits.trailing_zeros() as usize; - bits &= bits - 1; - ptrs[i] = unsafe { xs.as_mut_ptr().add(idx) }; - i += 1; - } - - // SAFETY: - // - indices are distinct (bitmask) - // - derived from same slice - - // should be zero-cost after inlining - let refs = unsafe { ptrs.map(|p| &mut *p) }; - - f(refs) - } - // check for node-sharing first if all_active_share(zs, active) { let z0 = first_active_mut(zs, active); - if let Some(v) = P::combine_n_times(lift(z0.val()), active.count_ones() as usize) { + let n = active.count_ones() as usize; + if let Some(v) = P::combine_n_times(lift(z0.val()), n) { out.set_val(v); } - P::on_id(z0, out); + P::on_id(z0, n, out); return; } @@ -1316,10 +1498,10 @@ where let mut bytes = [None; N]; let mut masks = [ByteMask::EMPTY; N]; - for (i, z) in only_active(zs, active) { - masks[i] = z.child_mask(); + for_each_bit(active, |i| { + masks[i] = zs[i].child_mask(); bytes[i] = masks[i].indexed_bit::(0); - } + }); // At each node, the algorithm: // @@ -1350,7 +1532,7 @@ where let mut frontier = 0u64; let mut next = None; - for i in active_bits::(active) { + for_each_bit(active, |i| { if let Some(b) = bytes[i] { match min { None => { @@ -1373,9 +1555,12 @@ where } } } - } + }); - debug_assert!(frontier <= active); + unsafe { + // SAFETY: frontier can be at most (1 | 1 << 1 | 1 << 2 | .. | 1 << (popcount(active))) + std::hint::assert_unchecked(frontier <= active); + } match min { None => { @@ -1383,6 +1568,7 @@ where } Some(a) => { // Dispatch + let cnt = frontier.count_ones() as usize; // - Case A: full match (frontier == all bits) if frontier == active { @@ -1396,12 +1582,10 @@ where // check structural sharing first if all_active_share(zs, active) { let z0 = first_active_mut(zs, active); - if let Some(v) = - P::combine_n_times(lift(z0.val()), active.count_ones() as usize) - { + if let Some(v) = P::combine_n_times(lift(z0.val()), cnt) { out.set_val(v); } - P::on_id(z0, out); + P::on_id(z0, cnt, out); for_each_bit(active, |i| { zs[i].ascend_byte(); @@ -1424,7 +1608,6 @@ where continue 'merge_level; } - let cnt = frontier.count_ones(); // - Case B: singleton (|frontier| = 1) if (cnt == 1) { let i = frontier.trailing_zeros() as usize; @@ -1524,129 +1707,494 @@ where } } -pub fn zipper_merge_dnf(clauses: &mut [&mut [Z]; M], out: &mut Out) -where - V: Lattice + Clone + Send + Sync + Unpin, - A: Allocator, - Z: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, - Out: ZipperWriting, -{ - #[inline(always)] - fn clause_mask(zs: &[Z]) -> ByteMask - where - Z: Zipper, - { - if zs.is_empty() { - return ByteMask::EMPTY; - }; - zs.iter() - .try_fold(ByteMask::FULL, |mut mask, z| { - mask &= z.child_mask(); - if mask.is_empty_mask() { - None - } else { - Some(mask) - } - }) - .unwrap_or(ByteMask::EMPTY) - } +/// A conjunction clause in a Disjunctive Normal Form (DNF) expression. +/// +/// A clause is represented as a bitmask over a fixed universe of `N` input +/// zippers. Bit `i` is set iff zipper `i` participates in the conjunction. +/// +/// For example, for `N = 4`: +/// +/// ```text +/// {0,2} => 0b0101 +/// {1,3} => 0b1010 +/// ``` +/// +/// The DNF expression +/// +/// ```text +/// (x₀ ∧ x₂) ∨ (x₁ ∧ x₃) +/// ``` +/// +/// can therefore be represented as: +/// +/// ```ignore +/// [ +/// Clause::<4>::from_indices([0, 2]), +/// Clause::<4>::from_indices([1, 3]), +/// ] +/// ``` +/// +/// All indices are validated at construction time, guaranteeing that no bit +/// outside the range `[0, N)` is ever set. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct Clause { + members: u64, +} - #[inline(always)] - fn clause_value(zs: &[Z]) -> Option - where - V: Lattice + Clone, - Z: ZipperValues, - { - Meet::combine_n(zs.iter().map(|z| lift(z.val()))) - } +impl Clause { + pub const EMPTY: Self = Self { members: 0 }; + pub const FULL: Self = Self { + members: if N == 64 { u64::MAX } else { (1u64 << N) - 1 }, + }; - fn active_clauses_value(clauses: &[&mut [Z]; M], active: u64) -> Option - where - V: Lattice + Clone, - Z: ZipperValues, - { - Join::combine_n( - only_active(clauses, active).map(|(_, zs)| clause_value(zs).map(Cow::Owned)), - ) + /// Creates a clause from a raw bitmask. + /// + /// # Panics + /// + /// Panics if the mask references a zipper index greater than or equal to `N`. + /// + /// # Examples + /// + /// ```ignore + /// let clause = Clause::<4>::from_mask(0b0101); + /// ``` + #[inline] + pub const fn from_mask(mask: u64) -> Self { + assert!(N > 0 && N <= 64); + assert!(mask >> N == 0); + + Self { members: mask } } - #[inline(always)] - fn compute_masks( - clauses: &[&mut [Z]; M], - active: u64, - clause_masks: &mut [ByteMask; M], - ) -> ByteMask - where - Z: Zipper, - { - let mut global = ByteMask::EMPTY; + /// Creates a clause containing exactly one zipper. + /// + /// # Examples + /// + /// ```ignore + /// let clause = Clause::<8>::singleton(3); + /// ``` + /// + /// corresponds to: + /// + /// ```text + /// x₃ + /// ``` + #[inline] + pub const fn singleton(i: usize) -> Self { + assert!(i < N); + Self::from_mask(1 << i) + } - for (i, zs) in only_active(clauses, active) { - let m = clause_mask(zs); + /// Creates a clause from a collection of zipper indices. + /// + /// # Examples + /// + /// ```ignore + /// let clause = Clause::<5>::new(&[0, 2, 4]); + /// ``` + /// + /// corresponds to: + /// + /// ```text + /// x₀ ∧ x₂ ∧ x₄ + /// ``` + /// + /// # Panics + /// + /// Panics if any index is greater than or equal to `N`. + #[inline] + pub fn new(indices: &[usize]) -> Self { + let mut mask = 0; - clause_masks[i] = m; - global |= m; + for &i in indices { + assert!(i < N); + mask |= 1u64 << i; } - global + Self::from_mask(mask) } - fn zipper_merge_dnf_branch( - clauses: &mut [&mut [Z]; M], - active: u64, - out: &mut Out, - ) where - V: Lattice + Clone + Send + Sync + Unpin, - A: Allocator, - Z: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, - Out: ZipperWriting, - { - assert!(active >> M == 0); + pub const fn pair(i: usize, j: usize) -> Self { + assert!(i < N); + assert!(j < N); - // ------------------------------------------------- - // Single clause fast path - // ------------------------------------------------- - if active.count_ones() == 1 { - let single_clause = first_active_mut(clauses, active); - match single_clause { - [z0] => { - if let Some(v) = z0.val() { - out.set_val(v.clone()); - } - Meet::on_id(z0, out); - return; - } - [z0, z1] => { - zipper_meet(z0, z1, out); - return; - } - [z0, z1, z2] => { - zipper_meet3(z0, z1, z2, out); - return; - } - [z0, z1, z2, z3] => { - zipper_merge4::(z0, z1, z2, z3, out); - return; - } - _ => {} // do nothing special - } - } + Self::from_mask((1u64 << i) | (1u64 << j)) + } - let mut clause_masks = [ByteMask::EMPTY; M]; - let mut depth = 0; + /// Creates a clause from a collection of zipper indices. + /// + /// # Examples + /// + /// ```ignore + /// let clause = Clause::<5>::from_indices([0, 2, 4]); + /// ``` + /// + /// corresponds to: + /// + /// ```text + /// x₀ ∧ x₂ ∧ x₄ + /// ``` + /// + /// # Panics + /// + /// Panics if any index is greater than or equal to `N`. + pub const fn from_indices(indices: [usize; K]) -> Self { + let mut mask = 0u64; + let mut i = 0; - // ------------------------------------------------- - // Emit values - // ------------------------------------------------- + while i < K { + let idx = indices[i]; - if let Some(v) = active_clauses_value(clauses, active) { - out.set_val(v); + assert!(idx < N); + + mask |= 1u64 << idx; + i += 1; } - // ------------------------------------------------- + + Self::from_mask(mask) + } + + /// Returns the internal membership bitmask. + /// + /// Bit `i` is set iff zipper `i` participates in this clause. + /// + /// This operation is constant-time. + #[inline(always)] + pub const fn members(self) -> u64 { + debug_assert!(self.members >> N == 0); + self.members + } + + pub const fn len(self) -> usize { + self.members.count_ones() as usize + } + + pub const fn is_empty(self) -> bool { + self.members == 0 + } +} + +/// Constructs a [`Clause`] using zipper indices. +/// +/// # Examples +/// +/// ```ignore +/// let clause = clause![0, 2, 4]; +/// ``` +/// +/// which is equivalent to: +/// +/// ```ignore +/// Clause::::from_indices([0, 2, 4]) +/// ``` +/// +/// This macro is primarily intended for constructing DNF expressions in a +/// concise and readable form. +/// +/// ```ignore +/// let dnf = [ +/// clause![0, 1], +/// clause![0, 2], +/// clause![1, 2], +/// ]; +/// ``` +#[macro_export] +macro_rules! clause { + ($($i:expr),+ $(,)?) => { + $crate::Clause::from_indices([$($i),+]) + }; +} + +/// Constructs a DNF expression as an array of [`Clause`] values. +/// +/// Each inner bracket denotes a conjunction clause, specified by the +/// indices of the participating zippers. +/// +/// # Examples +/// +/// Majority-of-three: +/// +/// ```ignore +/// let dnf = dnf![ +/// [0, 1], +/// [0, 2], +/// [1, 2], +/// ]; +/// ``` +/// +/// corresponds to: +/// +/// ```text +/// (x₀ ∧ x₁) +/// ∨ (x₀ ∧ x₂) +/// ∨ (x₁ ∧ x₂) +/// ``` +/// +/// A larger example: +/// +/// ```ignore +/// let dnf = dnf![ +/// [0], +/// [1, 2], +/// [0, 3, 4], +/// ]; +/// ``` +/// +/// corresponds to: +/// +/// ```text +/// x₀ +/// ∨ (x₁ ∧ x₂) +/// ∨ (x₀ ∧ x₃ ∧ x₄) +/// ``` +/// +/// The resulting value can be passed directly to [`zipper_merge_dnf`]: +/// +/// ```ignore +/// let clauses = dnf![ +/// [0, 1], +/// [0, 2], +/// [1, 2], +/// ]; +/// +/// zipper_merge_dnf::<_, _, _, _, 3, 3>( +/// &mut [x, y, z], +/// clauses, +/// out, +/// ); +/// ``` +/// +/// # Expansion +/// +/// ```ignore +/// dnf![ +/// [0, 1], +/// [2, 3], +/// ] +/// ``` +/// +/// expands approximately to: +/// +/// ```ignore +/// [ +/// Clause::from_indices([0, 1]), +/// Clause::from_indices([2, 3]), +/// ] +/// ``` +#[macro_export] +macro_rules! dnf { + ( + $( + [$($idx:expr),*] + ),* $(,)? + ) => { + [ + $( + Clause::from_indices([$($idx),*]) + ),* + ] + }; +} + +/// Evaluates a monotone Boolean expression in Disjunctive Normal Form (DNF) +/// over a collection of input zippers. +/// +/// Each clause represents a conjunction (`Meet`) of selected input zippers, +/// while the final result is the disjunction (`Join`) of all clauses: +/// +/// ```text +/// (Clause₀) ∨ (Clause₁) ∨ ... ∨ (Clauseₘ) +/// ``` +/// +/// where each clause is interpreted as: +/// +/// ```text +/// xᵢ ∧ xⱼ ∧ ... +/// ``` +/// +/// The algorithm traverses the input tries simultaneously using zipper +/// operations and emits the resulting trie into `out`. +/// +/// # Example +/// +/// Majority-of-three can be expressed as: +/// +/// ```text +/// (x ∧ y) ∨ (x ∧ z) ∨ (y ∧ z) +/// ``` +/// +/// ```ignore +/// let clauses = [ +/// clause![0, 1], +/// clause![0, 2], +/// clause![1, 2], +/// ]; +/// +/// zipper_merge_dnf::<_, _, _, _, 3, 3>( +/// &mut [x, y, z], +/// clauses, +/// out, +/// ); +/// ``` +/// +/// # Complexity +/// +/// The traversal is output-sensitive and explores only trie regions that are +/// reachable through at least one active clause. +/// +/// Whenever all currently active clauses descend through the same byte, the +/// algorithm performs an iterative tail descent without recursion. Recursive +/// calls occur only when the active clause set splits. +/// +/// # Optimizations +/// +/// * Single active clauses are dispatched to specialized meet +/// implementations (`zipper_meet`, `zipper_meet3`, `zipper_merge4`, ...). +/// * Zippers shared between multiple clauses are descended only once. +/// * Long common paths are traversed iteratively without recursion. +/// +/// # Panics +/// +/// Panics if `N == 0`, `M == 0`, or either exceeds 64. +pub fn zipper_merge_dnf( + zs: &mut [Z; N], + clauses: [Clause; M], + out: &mut Out, +) where + V: Lattice + Clone + Send + Sync + Unpin, + A: Allocator, + Z: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, + Out: ZipperWriting, +{ + #[inline(always)] + fn clause_mask(zs: &[Z; N], clause: &Clause) -> ByteMask + where + Z: Zipper, + { + if clause.is_empty() { + return ByteMask::EMPTY; + }; + active_refs(zs, clause.members()) + .try_fold(ByteMask::FULL, |mut mask, z| { + mask &= z.child_mask(); + if mask.is_empty_mask() { + None + } else { + Some(mask) + } + }) + .unwrap_or(ByteMask::EMPTY) + } + + #[inline(always)] + fn clause_value(zs: &[Z; N], clause: &Clause) -> Option + where + V: Lattice + Clone, + Z: ZipperValues, + { + Meet::combine_n(active_refs(zs, clause.members()).map(|z| lift(z.val()))) + } + + fn active_clauses_value( + zs: &[Z; N], + clauses: &[Clause; M], + active: u64, + ) -> Option + where + V: Lattice + Clone, + Z: ZipperValues, + { + Join::combine_n( + active_refs(clauses, active).map(|clause| clause_value(zs, clause).map(Cow::Owned)), + ) + } + + #[inline(always)] + fn compute_masks( + zs: &[Z; N], + clauses: &[Clause; M], + active: u64, + clause_masks: &mut [ByteMask; M], + ) -> ByteMask + where + Z: Zipper, + { + let mut global = ByteMask::EMPTY; + + for_each_bit(active, |i| { + let m = clause_mask(zs, &clauses[i]); + + clause_masks[i] = m; + global |= m; + }); + + global + } + + fn zipper_merge_dnf_branch( + zs: &mut [Z; N], + clauses: &[Clause; M], + active: u64, + out: &mut Out, + ) where + V: Lattice + Clone + Send + Sync + Unpin, + A: Allocator, + Z: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, + Out: ZipperWriting, + { + assert!(active >> M == 0); + + // ------------------------------------------------- + // Single clause fast path + // ------------------------------------------------- + if active.count_ones() == 1 { + let single_clause = first_active(clauses, active); + match single_clause.len() { + 1 => { + let z0 = first_active_mut(zs, single_clause.members()); + if let Some(v) = z0.val() { + out.set_val(v.clone()); + } + Meet::on_id(z0, 1, out); + return; + } + 2 => { + with_k::<2, _, _>(zs, single_clause.members(), |[z0, z1]| { + zipper_meet(z0, z1, out); + }); + return; + } + 3 => { + with_k::<3, _, _>(zs, single_clause.members(), |[z0, z1, z2]| { + zipper_meet3(z0, z1, z2, out); + }); + return; + } + 4 => { + with_k::<4, _, _>(zs, single_clause.members(), |[z0, z1, z2, z3]| { + zipper_merge4::(z0, z1, z2, z3, out); + }); + return; + } + _ => {} // do nothing special + } + } + + let mut clause_masks = [ByteMask::EMPTY; M]; + let mut depth = 0; + + // ------------------------------------------------- + // Emit values + // ------------------------------------------------- + + if let Some(v) = active_clauses_value(zs, clauses, active) { + out.set_val(v); + } + // ------------------------------------------------- // Compute clause masks // ------------------------------------------------- - let mut global = compute_masks(clauses, active, &mut clause_masks); + let mut global = compute_masks(zs, clauses, active, &mut clause_masks); let mut next = global.indexed_bit::(0); 'descend: loop { // ------------------------------------------------- @@ -1656,17 +2204,23 @@ where out.descend_to_byte(byte); let mut sub_active = 0u64; + let mut participating = 0u64; // descend participating clauses for_each_bit(active, |i| { if clause_masks[i].test_bit(byte) { sub_active |= 1 << i; - - for z in clauses[i].iter_mut() { - z.descend_to_byte(byte); - } + participating |= clauses[i].members(); } }); + unsafe { + // SAFETY: The value of particpating preserves the invariant as c_1 | .. | c_i, + // where c_x >> N == 0 + std::hint::assert_unchecked(participating >> N == 0); + } + for_each_bit(participating, |i| { + zs[i].descend_to_byte(byte); + }); // ------------------------------------------------- // Tail-descent fast path @@ -1675,11 +2229,11 @@ where if sub_active == active { depth += 1; - if let Some(v) = active_clauses_value(clauses, active) { + if let Some(v) = active_clauses_value(zs, clauses, active) { out.set_val(v); } - global = compute_masks(clauses, active, &mut clause_masks); + global = compute_masks(zs, clauses, active, &mut clause_masks); next = global.indexed_bit::(0); continue 'descend; } @@ -1688,13 +2242,11 @@ where // Branching recursion // ------------------------------------------------- - zipper_merge_dnf_branch(clauses, sub_active, out); + zipper_merge_dnf_branch(zs, clauses, sub_active, out); // ascend - for_each_bit(sub_active, |i| { - for z in clauses[i].iter_mut() { - z.ascend_byte(); - } + for_each_bit(participating, |i| { + zs[i].ascend_byte(); }); out.ascend_byte(); @@ -1709,30 +2261,70 @@ where break; } - let byte_from = first_active_mut(clauses, active) - .first() - .and_then(|z| z.path().last().copied()) + let byte_from = *first_active(zs, first_active(clauses, active).members()) + .path() + .last() .expect("non-empty path at depth > 0"); + let mut active_zippers = 0; for_each_bit(active, |i| { - for z in clauses[i].iter_mut() { - z.ascend_byte(); - } + active_zippers |= clauses[i].members(); }); + for_each_bit(active_zippers, |i| { + zs[i].ascend_byte(); + }); out.ascend_byte(); depth -= 1; // recompute masks after ascent - global = compute_masks(clauses, active, &mut clause_masks); + global = compute_masks(zs, clauses, active, &mut clause_masks); // resume sibling traversal next = global.next_bit(byte_from); } } - debug_assert!(M > 0 && M <= 64); - zipper_merge_dnf_branch(clauses, ((1 << M) - 1), out); + assert!(N > 0 && N <= 64); + assert!(M > 0 && M <= 64); + zipper_merge_dnf_branch(zs, &clauses, ((1 << M) - 1), out); +} + +/// Computes the majority (2-of-3) combination of three zippers. +/// +/// A value is present in the result iff it is present in at least two +/// of the three inputs. +/// +/// Algebraically: +/// +/// ```text +/// maj(a, b, c) +/// = (a ∧ b) +/// ∨ (a ∧ c) +/// ∨ (b ∧ c) +/// ``` +/// +/// This operation is monotone and can be expressed as a Disjunctive +/// Normal Form (DNF) evaluated by [`zipper_merge_dnf`]. +/// +/// The implementation reuses the DNF merge engine rather than performing +/// a specialized traversal. +/// +/// This is the lattice analogue of the Boolean majority function. +pub fn zipper_majority(x: Z, y: Z, z: Z, out: &mut Out) +where + V: Lattice + Clone + Send + Sync + Unpin, + A: Allocator, + Z: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, + Out: ZipperWriting, +{ + const MAJORITY: [Clause<3>; 3] = [ + Clause::from_mask(0b011), // x ∧ y + Clause::from_mask(0b101), // x ∧ z + Clause::from_mask(0b110), // y ∧ z + ]; + + zipper_merge_dnf(&mut [x, y, z], MAJORITY, out); } // ==================== JOIN ==================== @@ -1755,7 +2347,7 @@ impl MergePolicy for Join { } #[inline] - fn on_id(z: &mut Z, out: &mut Out) + fn on_id(z: &mut Z, _n: usize, out: &mut Out) where A: Allocator, Z: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, @@ -1812,7 +2404,7 @@ impl MergePolicy for Meet { } #[inline(always)] - fn on_id(z: &mut Z, out: &mut Out) + fn on_id(z: &mut Z, _n: usize, out: &mut Out) where A: Allocator, Z: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, @@ -1934,7 +2526,7 @@ impl MergePolicy for Subtract { } #[inline] - fn on_id(_z: &mut Z, _out: &mut Out) + fn on_id(_z: &mut Z, _n: usize, _out: &mut Out) where A: Allocator, Z: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, @@ -1991,6 +2583,95 @@ fn subtract_impl<'a, V: DistributiveLattice + Clone>( } } +// ==================== XOR ==================== + +struct Xor; +impl MergePolicy for Xor { + #[inline(always)] + fn on_single(z: &mut Z, _mask: u64, range: ByteMask, out: &mut Out) + where + A: Allocator, + Z: ZipperInfallibleSubtries + ZipperMoving, + Out: ZipperWriting, + { + out.graft_masked_branches(z, range, false) + } + + #[inline(always)] + fn descend_on_some_equal(_mask: u64) -> bool { + // the traversal shape essentially the same as join + true + } + + #[inline(always)] + fn on_id(z: &mut Z, n: usize, out: &mut Out) + where + A: Allocator, + Z: ZipperInfallibleSubtries + ZipperConcrete + ZipperMoving, + Out: ZipperWriting, + { + // an element belongs to A△A△A ... iff it belongs to an odd number of the sets. + if n % 2 == 1 { + out.graft(z) + } + } +} + +impl ValuePolicy for Xor { + fn combine_impl<'a>(l: Option>, r: Option>) -> Option> { + match (l, r) { + (None, x) | (x, None) => x, + + (Some(a), Some(b)) => match a.pjoin(&b) { + AlgebraicResult::None => None, + AlgebraicResult::Identity(join) => match a.pmeet(&b) { + AlgebraicResult::None => { + if join & SELF_IDENT != 0 { + Some(a) + } else { + Some(b) + } + } + AlgebraicResult::Identity(meet) => { + if join == meet { + None + } else if join & SELF_IDENT != 0 { + subtract_impl(a, b) + } else { + subtract_impl(b, a) + } + } + AlgebraicResult::Element(meet) => { + if join & SELF_IDENT != 0 { + subtract_impl(a, Cow::Owned(meet)) + } else { + subtract_impl(b, Cow::Owned(meet)) + } + } + }, + AlgebraicResult::Element(join) => match a.pmeet(&b) { + AlgebraicResult::None => Some(Cow::Owned(join)), + AlgebraicResult::Identity(meet) => { + if meet & SELF_IDENT != 0 { + subtract_impl(Cow::Owned(join), a) + } else { + subtract_impl(Cow::Owned(join), b) + } + } + AlgebraicResult::Element(meet) => { + subtract_impl(Cow::Owned(join), Cow::Owned(meet)) + } + }, + }, + } + } + + fn combine_n_times(val: Option>, n: usize) -> Option { + // an element belongs to A△A△A ... iff it belongs to an odd number of the sets. + if n % 2 == 1 { unlift(val) } else { None } + } +} + mod zipper_algebra_poly { // ==================== Machinery for zipper_merge_n ==================== use crate as pathmap; @@ -2095,7 +2776,17 @@ mod zipper_algebra_poly { self.merge_n::(out); } - fn merge_n

(self, out: &mut Out) + /// Performs an N-way ordered symmetric difference of radix-256 trie zippers using a stackless traversal. + /// + /// This function generalizes pairwise [`super::zipper_xor`] to an arbitrary number of input tries, + fn xor_n(self, out: &mut Out) + where + V: Lattice + DistributiveLattice, + { + self.merge_n::(out); + } + + fn merge_n

(self, out: &mut Out) where P: super::MergePolicy + super::ValuePolicy; } @@ -2328,6 +3019,28 @@ mod zipper_algebra_poly { ( $( &mut $z ),+ ).subtract_n(&mut $out) }}; } + + /// Performs an N-ary zipper symmetric difference by borrowing all inputs mutably + /// and forwarding them to [`ZipperMergeF::xor_n`]. + /// + /// # Example + /// ```ignore + /// zipper_xor_n!(z1, z2, z3 => out); + /// ``` + /// + /// Expands roughly to: + /// ```ignore + /// (&mut z1, &mut z2, &mut z3).xor_n(&mut out) + /// ``` + /// + /// # See also + /// [`ZipperMergeF::xor_n`] + #[macro_export] + macro_rules! zipper_xor_n { + ( $($z:ident),+ => $out:ident ) => {{ + ( $( &mut $z ),+ ).xor_n(&mut $out) + }}; +} } #[cfg(test)] @@ -2367,8 +3080,8 @@ mod tests { 'x, T: IntoIterator, F: for<'a> FnOnce( - &mut ReadZipperUntracked<'a, 'x, u64>, - &mut ReadZipperUntracked<'a, 'x, u64>, + ReadZipperUntracked<'a, 'x, u64>, + ReadZipperUntracked<'a, 'x, u64>, &mut WriteZipperUntracked<'a, 'x, u64>, ), >( @@ -2384,7 +3097,7 @@ mod tests { let mut rhs = right.read_zipper(); let mut out = result.write_zipper(); - op(&mut lhs, &mut rhs, &mut out); + op(lhs, rhs, &mut out); assert_trie(expected.into_iter().copied(), result); } @@ -2393,9 +3106,9 @@ mod tests { 'x, T: IntoIterator, F: for<'a> FnOnce( - &mut ReadZipperUntracked<'a, 'x, u64>, - &mut ReadZipperUntracked<'a, 'x, u64>, - &mut ReadZipperUntracked<'a, 'x, u64>, + ReadZipperUntracked<'a, 'x, u64>, + ReadZipperUntracked<'a, 'x, u64>, + ReadZipperUntracked<'a, 'x, u64>, &mut WriteZipperUntracked<'a, 'x, u64>, ), >( @@ -2412,7 +3125,7 @@ mod tests { let mut rhs = right.read_zipper(); let mut out = result.write_zipper(); - op(&mut lhs, &mut mid, &mut rhs, &mut out); + op(lhs, mid, rhs, &mut out); assert_trie(expected.into_iter().copied(), result); } @@ -2935,7 +3648,7 @@ mod tests { check2( &DISJOINT_PATHS, &[DISJOINT_PATHS.0, DISJOINT_PATHS.1].concat(), - |lhs, rhs, out| lhs.join(rhs, out), + |mut lhs, mut rhs, out| lhs.join(&mut rhs, out), ); } @@ -2944,7 +3657,7 @@ mod tests { check3( &DISJOINT_PATHS_3, &[DISJOINT_PATHS_3.0, DISJOINT_PATHS_3.1, DISJOINT_PATHS_3.2].concat(), - |lhs, mid, rhs, out| zipper_join3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| zipper_join3(&mut lhs, &mut mid, &mut rhs, out), ); } @@ -2970,7 +3683,7 @@ mod tests { check2( &PATHS_WITH_SHARED_PREFIX, &[PATHS_WITH_SHARED_PREFIX.0, PATHS_WITH_SHARED_PREFIX.1].concat(), - |lhs, rhs, out| lhs.join(rhs, out), + |mut lhs, mut rhs, out| lhs.join(&mut rhs, out), ); } @@ -2984,7 +3697,7 @@ mod tests { PATHS_WITH_SHARED_PREFIX_3.2, ] .concat(), - |lhs, mid, rhs, out| zipper_join3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| zipper_join3(&mut lhs, &mut mid, &mut rhs, out), ); } @@ -3010,7 +3723,7 @@ mod tests { check2( &INTERLEAVING_PATHS, &[INTERLEAVING_PATHS.0, INTERLEAVING_PATHS.1].concat(), - |lhs, rhs, out| lhs.join(rhs, out), + |mut lhs, mut rhs, out| lhs.join(&mut rhs, out), ); } @@ -3024,7 +3737,7 @@ mod tests { INTERLEAVING_PATHS_3.2, ] .concat(), - |lhs, mid, rhs, out| zipper_join3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| zipper_join3(&mut lhs, &mut mid, &mut rhs, out), ); } @@ -3047,9 +3760,11 @@ mod tests { #[test] fn test_one_side_empty_at_many_levels() { - check2(&ONE_SIDED_PATHS, ONE_SIDED_PATHS.0, |lhs, rhs, out| { - lhs.join(rhs, out) - }); + check2( + &ONE_SIDED_PATHS, + ONE_SIDED_PATHS.0, + |mut lhs, mut rhs, out| lhs.join(&mut rhs, out), + ); } #[test] @@ -3057,7 +3772,7 @@ mod tests { check3( &ONE_SIDED_PATHS_3, ONE_SIDED_PATHS_3.0, - |lhs, mid, rhs, out| zipper_join3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| zipper_join3(&mut lhs, &mut mid, &mut rhs, out), ); } @@ -3075,7 +3790,7 @@ mod tests { check2( &ALMOST_IDENTICAL_PATHS, ALMOST_IDENTICAL_PATHS.0, - |lhs, rhs, out| lhs.join(rhs, out), + |mut lhs, mut rhs, out| lhs.join(&mut rhs, out), ); } @@ -3084,7 +3799,7 @@ mod tests { check3( &ALMOST_IDENTICAL_PATHS_3, ALMOST_IDENTICAL_PATHS_3.0, - |lhs, mid, rhs, out| zipper_join3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| zipper_join3(&mut lhs, &mut mid, &mut rhs, out), ); } @@ -3099,8 +3814,12 @@ mod tests { #[test] fn test_one_side_empty() { - check2(&LHS_EMPTY, LHS_EMPTY.1, |lhs, rhs, out| lhs.join(rhs, out)); - check2(&RHS_EMPTY, RHS_EMPTY.0, |lhs, rhs, out| lhs.join(rhs, out)); + check2(&LHS_EMPTY, LHS_EMPTY.1, |mut lhs, mut rhs, out| { + lhs.join(&mut rhs, out) + }); + check2(&RHS_EMPTY, RHS_EMPTY.0, |mut lhs, mut rhs, out| { + lhs.join(&mut rhs, out) + }); } #[test] @@ -3108,17 +3827,17 @@ mod tests { check3( &LHS_EMPTY_3, &[LHS_EMPTY_3.1, LHS_EMPTY_3.2].concat(), - |lhs, mid, rhs, out| zipper_join3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| zipper_join3(&mut lhs, &mut mid, &mut rhs, out), ); check3( &MID_EMPTY, &[MID_EMPTY.0, MID_EMPTY.2].concat(), - |lhs, mid, rhs, out| zipper_join3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| zipper_join3(&mut lhs, &mut mid, &mut rhs, out), ); check3( &RHS_EMPTY_3, &[RHS_EMPTY_3.0, RHS_EMPTY_3.1].concat(), - |lhs, mid, rhs, out| zipper_join3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| zipper_join3(&mut lhs, &mut mid, &mut rhs, out), ); } @@ -3174,7 +3893,7 @@ mod tests { check2( &PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN, expected, - |lhs, rhs, out| lhs.join(rhs, out), + |mut lhs, mut rhs, out| lhs.join(&mut rhs, out), ); } @@ -3192,7 +3911,7 @@ mod tests { check3( &PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN_3, expected, - |lhs, mid, rhs, out| zipper_join3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| zipper_join3(&mut lhs, &mut mid, &mut rhs, out), ); } @@ -3225,7 +3944,7 @@ mod tests { check2( &ZIGZAG_PATHS, &[ZIGZAG_PATHS.0, ZIGZAG_PATHS.1].concat(), - |lhs, rhs, out| lhs.join(rhs, out), + |mut lhs, mut rhs, out| lhs.join(&mut rhs, out), ); } @@ -3234,7 +3953,7 @@ mod tests { check3( &ZIGZAG_PATHS_3, &[ZIGZAG_PATHS_3.0, ZIGZAG_PATHS_3.1, ZIGZAG_PATHS_3.2].concat(), - |lhs, mid, rhs, out| zipper_join3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| zipper_join3(&mut lhs, &mut mid, &mut rhs, out), ); } @@ -3243,7 +3962,7 @@ mod tests { check2( &PATHS_WITH_ROOT_VALS_AND_CHILDREN, PATHS_WITH_ROOT_VALS_AND_CHILDREN.0, - |lhs, rhs, out| lhs.join(rhs, out), + |mut lhs, mut rhs, out| lhs.join(&mut rhs, out), ); } @@ -3252,7 +3971,7 @@ mod tests { check3( &PATHS_WITH_ROOT_VALS_AND_CHILDREN_3, PATHS_WITH_ROOT_VALS_AND_CHILDREN_3.0, - |lhs, mid, rhs, out| zipper_join3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| zipper_join3(&mut lhs, &mut mid, &mut rhs, out), ); } @@ -3275,15 +3994,15 @@ mod tests { #[test] fn test_disjoint() { - check2(&DISJOINT_PATHS, [], |lhs, rhs, out| { - lhs.meet(rhs, out); + check2(&DISJOINT_PATHS, [], |mut lhs, mut rhs, out| { + lhs.meet(&mut rhs, out); }); } #[test] fn test_disjoint3() { - check3(&DISJOINT_PATHS_3, [], |lhs, mid, rhs, out| { - zipper_meet3(lhs, mid, rhs, out); + check3(&DISJOINT_PATHS_3, [], |mut lhs, mut mid, mut rhs, out| { + zipper_meet3(&mut lhs, &mut mid, &mut rhs, out); }); } @@ -3298,16 +4017,20 @@ mod tests { #[test] fn test_deep_shared_prefix_then_split() { - check2(&PATHS_WITH_SHARED_PREFIX, [], |lhs, rhs, out| { - lhs.meet(rhs, out); + check2(&PATHS_WITH_SHARED_PREFIX, [], |mut lhs, mut rhs, out| { + lhs.meet(&mut rhs, out); }); } #[test] fn test_deep_shared_prefix_then_split3() { - check3(&PATHS_WITH_SHARED_PREFIX_3, [], |lhs, mid, rhs, out| { - zipper_meet3(lhs, mid, rhs, out); - }); + check3( + &PATHS_WITH_SHARED_PREFIX_3, + [], + |mut lhs, mut mid, mut rhs, out| { + zipper_meet3(&mut lhs, &mut mid, &mut rhs, out); + }, + ); } #[test] @@ -3321,16 +4044,20 @@ mod tests { #[test] fn test_interleaving_paths() { - check2(&INTERLEAVING_PATHS, [], |lhs, rhs, out| { - lhs.meet(rhs, out); + check2(&INTERLEAVING_PATHS, [], |mut lhs, mut rhs, out| { + lhs.meet(&mut rhs, out); }); } #[test] fn test_interleaving_paths3() { - check3(&INTERLEAVING_PATHS_3, [], |lhs, mid, rhs, out| { - zipper_meet3(lhs, mid, rhs, out); - }); + check3( + &INTERLEAVING_PATHS_3, + [], + |mut lhs, mut mid, mut rhs, out| { + zipper_meet3(&mut lhs, &mut mid, &mut rhs, out); + }, + ); } #[test] @@ -3349,17 +4076,21 @@ mod tests { (&[0x00, 0x01, 0x02, 0x03], 3), (&[0x01, 0x02, 0x03, 0x04, 0x05], 8), ]; - check2(&ONE_SIDED_PATHS, expected, |lhs, rhs, out| { - lhs.meet(rhs, out); + check2(&ONE_SIDED_PATHS, expected, |mut lhs, mut rhs, out| { + lhs.meet(&mut rhs, out); }); } #[test] fn test_one_side_empty_at_many_levels3() { let expected: Paths = &[(&[0x00], 0)]; - check3(&ONE_SIDED_PATHS_3, expected, |lhs, mid, rhs, out| { - zipper_meet3(lhs, mid, rhs, out); - }); + check3( + &ONE_SIDED_PATHS_3, + expected, + |mut lhs, mut mid, mut rhs, out| { + zipper_meet3(&mut lhs, &mut mid, &mut rhs, out); + }, + ); } #[test] @@ -3376,16 +4107,22 @@ mod tests { check2( &ALMOST_IDENTICAL_PATHS, ALMOST_IDENTICAL_PATHS.1, - |lhs, rhs, out| lhs.meet(rhs, out), + |mut lhs, mut rhs, out| { + lhs.meet(&mut rhs, out); + }, ); } #[test] fn test_almost_identical_paths3() { let expected: Paths = &[(b"abcdefg", 0), (b"1", 4), (b"4", 7), (b"5", 8)]; - check3(&ALMOST_IDENTICAL_PATHS_3, expected, |lhs, mid, rhs, out| { - zipper_meet3(lhs, mid, rhs, out); - }); + check3( + &ALMOST_IDENTICAL_PATHS_3, + expected, + |mut lhs, mut mid, mut rhs, out| { + zipper_meet3(&mut lhs, &mut mid, &mut rhs, out); + }, + ); } #[test] @@ -3400,20 +4137,24 @@ mod tests { #[test] fn test_one_side_empty() { - check2(&LHS_EMPTY, [], |lhs, rhs, out| lhs.meet(rhs, out)); - check2(&RHS_EMPTY, [], |lhs, rhs, out| lhs.meet(rhs, out)); + check2(&LHS_EMPTY, [], |mut lhs, mut rhs, out| { + lhs.meet(&mut rhs, out); + }); + check2(&RHS_EMPTY, [], |mut lhs, mut rhs, out| { + lhs.meet(&mut rhs, out); + }); } #[test] fn test_one_side_empty3() { - check3(&LHS_EMPTY_3, [], |lhs, mid, rhs, out| { - zipper_meet3(lhs, mid, rhs, out) + check3(&LHS_EMPTY_3, [], |mut lhs, mut mid, mut rhs, out| { + zipper_meet3(&mut lhs, &mut mid, &mut rhs, out); }); - check3(&MID_EMPTY, [], |lhs, mid, rhs, out| { - zipper_meet3(lhs, mid, rhs, out) + check3(&MID_EMPTY, [], |mut lhs, mut mid, mut rhs, out| { + zipper_meet3(&mut lhs, &mut mid, &mut rhs, out); }); - check3(&RHS_EMPTY_3, [], |lhs, mid, rhs, out| { - zipper_meet3(lhs, mid, rhs, out) + check3(&RHS_EMPTY_3, [], |mut lhs, mut mid, mut rhs, out| { + zipper_meet3(&mut lhs, &mut mid, &mut rhs, out); }); } @@ -3442,7 +4183,9 @@ mod tests { check2( &PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN, expected, - |lhs, rhs, out| lhs.meet(rhs, out), + |mut lhs, mut rhs, out| { + lhs.meet(&mut rhs, out); + }, ); } @@ -3452,7 +4195,9 @@ mod tests { check3( &PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN_3, expected, - |lhs, mid, rhs, out| zipper_meet3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| { + zipper_meet3(&mut lhs, &mut mid, &mut rhs, out); + }, ); } @@ -3469,17 +4214,21 @@ mod tests { #[test] fn test_zigzag() { let expected: Paths = &[(&[2, 1], 2), (&[3], 3)]; - check2(&ZIGZAG_PATHS, expected, |lhs, rhs, out| { - lhs.meet(rhs, out); + check2(&ZIGZAG_PATHS, expected, |mut lhs, mut rhs, out| { + lhs.meet(&mut rhs, out); }); } #[test] fn test_zigzag3() { let expected: Paths = &[(&[2, 1], 2)]; - check3(&ZIGZAG_PATHS_3, expected, |lhs, mid, rhs, out| { - zipper_meet3(lhs, mid, rhs, out) - }); + check3( + &ZIGZAG_PATHS_3, + expected, + |mut lhs, mut mid, mut rhs, out| { + zipper_meet3(&mut lhs, &mut mid, &mut rhs, out); + }, + ); } #[test] @@ -3487,7 +4236,9 @@ mod tests { check2( &PATHS_WITH_ROOT_VALS_AND_CHILDREN, PATHS_WITH_ROOT_VALS_AND_CHILDREN.0, - |lhs, rhs, out| lhs.meet(rhs, out), + |mut lhs, mut rhs, out| { + lhs.meet(&mut rhs, out); + }, ); } @@ -3496,7 +4247,9 @@ mod tests { check3( &PATHS_WITH_ROOT_VALS_AND_CHILDREN_3, PATHS_WITH_ROOT_VALS_AND_CHILDREN.0, - |lhs, mid, rhs, out| zipper_meet3(lhs, mid, rhs, out), + |mut lhs, mut mid, mut rhs, out| { + zipper_meet3(&mut lhs, &mut mid, &mut rhs, out); + }, ); } @@ -3519,9 +4272,13 @@ mod tests { #[test] fn test_disjoint() { - check2(&DISJOINT_PATHS, DISJOINT_PATHS.0, |lhs, rhs, out| { - lhs.subtract(rhs, out); - }); + check2( + &DISJOINT_PATHS, + DISJOINT_PATHS.0, + |mut lhs, mut rhs, out| { + lhs.subtract(&mut rhs, out); + }, + ); } #[test] @@ -3529,8 +4286,8 @@ mod tests { check3( &DISJOINT_PATHS_3, DISJOINT_PATHS_3.0, - |lhs, mid, rhs, out| { - zipper_subtract3(lhs, mid, rhs, out); + |mut lhs, mut mid, mut rhs, out| { + zipper_subtract3(&mut lhs, &mut mid, &mut rhs, out); }, ); } @@ -3549,7 +4306,9 @@ mod tests { check2( &PATHS_WITH_SHARED_PREFIX, PATHS_WITH_SHARED_PREFIX.0, - |lhs, rhs, out| lhs.subtract(rhs, out), + |mut lhs, mut rhs, out| { + lhs.subtract(&mut rhs, out); + }, ); } @@ -3558,8 +4317,8 @@ mod tests { check3( &PATHS_WITH_SHARED_PREFIX_3, PATHS_WITH_SHARED_PREFIX_3.0, - |lhs, mid, rhs, out| { - zipper_subtract3(lhs, mid, rhs, out); + |mut lhs, mut mid, mut rhs, out| { + zipper_subtract3(&mut lhs, &mut mid, &mut rhs, out); }, ); } @@ -3577,8 +4336,346 @@ mod tests { fn test_interleaving_paths() { check2( &INTERLEAVING_PATHS, - INTERLEAVING_PATHS.0, - |lhs, rhs, out| lhs.subtract(rhs, out), + INTERLEAVING_PATHS.0, + |mut lhs, mut rhs, out| { + lhs.subtract(&mut rhs, out); + }, + ); + } + + #[test] + fn test_interleaving_paths3() { + check3( + &INTERLEAVING_PATHS_3, + INTERLEAVING_PATHS_3.0, + |mut lhs, mut mid, mut rhs, out| { + zipper_subtract3(&mut lhs, &mut mid, &mut rhs, out); + }, + ); + } + + #[test] + fn test_interleaving_paths_n() { + checkn( + &INTERLEAVING_PATHS_N, + INTERLEAVING_PATHS_N[0], + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + ); + } + + #[test] + fn test_one_side_empty_at_many_levels() { + let expected: Paths = &[ + (&[0x00, 0x01], 1), + (&[0x00, 0x01, 0x02], 2), + (&[0x00, 0x01, 0x02, 0x03], 3), + (&[0x01], 4), + (&[0x01, 0x02], 5), + (&[0x01, 0x02, 0x03], 6), + (&[0x01, 0x02, 0x03, 0x04], 7), + (&[0x01, 0x02, 0x03, 0x04, 0x05], 8), + (&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06], 9), + ]; + check2(&ONE_SIDED_PATHS, expected, |mut lhs, mut rhs, out| { + lhs.subtract(&mut rhs, out); + }); + } + + #[test] + fn test_one_side_empty_at_many_levels3() { + let expected: Paths = &[ + (&[0x00, 0x01], 1), + (&[0x00, 0x01, 0x02], 2), + (&[0x00, 0x01, 0x02, 0x03], 3), + (&[0x01], 4), + (&[0x01, 0x02], 5), + (&[0x01, 0x02, 0x03], 6), + (&[0x01, 0x02, 0x03, 0x04], 7), + (&[0x01, 0x02, 0x03, 0x04, 0x05], 8), + (&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06], 9), + ]; + check3( + &ONE_SIDED_PATHS_3, + expected, + |mut lhs, mut mid, mut rhs, out| { + zipper_subtract3(&mut lhs, &mut mid, &mut rhs, out); + }, + ); + } + + #[test] + fn test_one_side_empty_at_many_levels_n() { + let expected: Paths = &[ + (&[0x00, 0x01], 1), + (&[0x00, 0x01, 0x02], 2), + (&[0x00, 0x01, 0x02, 0x03], 3), + (&[0x01, 0x02, 0x03], 6), + (&[0x01, 0x02, 0x03, 0x04], 7), + (&[0x01, 0x02, 0x03, 0x04, 0x05], 8), + (&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06], 9), + ]; + checkn( + &ONE_SIDED_PATHS_N, + expected, + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + ); + } + + #[test] + fn test_almost_identical_paths() { + let expected: Paths = &[(b"hijklmnop", 1), (b"2", 5), (b"3", 6)]; + check2( + &ALMOST_IDENTICAL_PATHS, + expected, + |mut lhs, mut rhs, out| { + lhs.subtract(&mut rhs, out); + }, + ); + } + + #[test] + fn test_almost_identical_paths3() { + check3( + &ALMOST_IDENTICAL_PATHS_3, + [], + |mut lhs, mut mid, mut rhs, out| { + zipper_subtract3(&mut lhs, &mut mid, &mut rhs, out); + }, + ); + } + + #[test] + fn test_almost_identical_paths_n() { + checkn( + &ALMOST_IDENTICAL_PATHS_N, + [], + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + ); + } + + #[test] + fn test_one_side_empty() { + check2(&LHS_EMPTY, [], |mut lhs, mut rhs, out| { + lhs.subtract(&mut rhs, out); + }); + check2(&RHS_EMPTY, RHS_EMPTY.0, |mut lhs, mut rhs, out| { + lhs.subtract(&mut rhs, out); + }); + } + + #[test] + fn test_one_side_empty3() { + check3(&LHS_EMPTY_3, [], |mut lhs, mut mid, mut rhs, out| { + zipper_subtract3(&mut lhs, &mut mid, &mut rhs, out); + }); + check3(&MID_EMPTY, MID_EMPTY.0, |mut lhs, mut mid, mut rhs, out| { + zipper_subtract3(&mut lhs, &mut mid, &mut rhs, out); + }); + check3( + &RHS_EMPTY_3, + RHS_EMPTY_3.0, + |mut lhs, mut mid, mut rhs, out| { + zipper_subtract3(&mut lhs, &mut mid, &mut rhs, out); + }, + ); + } + + #[test] + fn test_one_side_empty_n() { + checkn( + &LHS_EMPTY_N, + [], + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + ); + checkn( + &MID_EMPTY_N, + MID_EMPTY_N[0], + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + ); + checkn( + &RHS_EMPTY_N, + RHS_EMPTY_N[0], + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + ); + } + + #[test] + fn test_exact_overlap_divergent_subtries() { + check2( + &PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN, + PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN.0, + |mut lhs, mut rhs, out| { + lhs.subtract(&mut rhs, out); + }, + ); + } + + #[test] + fn test_exact_overlap_divergent_subtries3() { + check3( + &PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN_3, + PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN_3.0, + |mut lhs, mut mid, mut rhs, out| { + zipper_subtract3(&mut lhs, &mut mid, &mut rhs, out); + }, + ); + } + + #[test] + fn test_exact_overlap_divergent_subtries_n() { + checkn( + &PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN_N, + PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN_N[0], + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + ); + } + + #[test] + fn test_zigzag() { + let expected: Paths = &[ + (&[1, 1], 0), + (&[2], 1), + (&[3, 2, 1], 4), + (&[4], 4), + (&[4, 3, 2, 1], 5), + ]; + check2(&ZIGZAG_PATHS, expected, |mut lhs, mut rhs, out| { + lhs.subtract(&mut rhs, out); + }); + } + + #[test] + fn test_zigzag3() { + let expected: Paths = &[(&[1, 1], 0), (&[3, 2, 1], 4), (&[4], 4)]; + check3( + &ZIGZAG_PATHS_3, + expected, + |mut lhs, mut mid, mut rhs, out| { + zipper_subtract3(&mut lhs, &mut mid, &mut rhs, out); + }, + ); + } + + #[test] + fn test_root_values() { + check2( + &PATHS_WITH_ROOT_VALS_AND_CHILDREN, + PATHS_WITH_ROOT_VALS_AND_CHILDREN.0, + |mut lhs, mut rhs, out| { + lhs.subtract(&mut rhs, out); + }, + ); + } + + #[test] + fn test_root_values3() { + check3( + &PATHS_WITH_ROOT_VALS_AND_CHILDREN_3, + PATHS_WITH_ROOT_VALS_AND_CHILDREN_3.0, + |mut lhs, mut mid, mut rhs, out| { + zipper_subtract3(&mut lhs, &mut mid, &mut rhs, out); + }, + ); + } + + #[test] + fn test_root_values_n() { + checkn( + &PATHS_WITH_ROOT_VALS_AND_CHILDREN_N, + PATHS_WITH_ROOT_VALS_AND_CHILDREN_N[0], + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + ); + } + } + + mod xor { + use super::*; + use crate::experimental::zipper_algebra::{ + ZipperAlgebraExt, ZipperMergeF, zipper_join, zipper_xor3, + }; + use crate::zipper_xor_n; + + #[test] + fn test_disjoint() { + check2( + &DISJOINT_PATHS, + &[DISJOINT_PATHS.0, DISJOINT_PATHS.1].concat(), + |mut lhs, mut rhs, out| lhs.xor(&mut rhs, out), + ); + } + + #[test] + fn test_disjoint3() { + check3( + &DISJOINT_PATHS_3, + &[DISJOINT_PATHS_3.0, DISJOINT_PATHS_3.1, DISJOINT_PATHS_3.2].concat(), + |mut lhs, mut mid, mut rhs, out| zipper_xor3(&mut lhs, &mut mid, &mut rhs, out), + ); + } + + #[test] + fn test_disjoint_n() { + checkn( + &DISJOINT_PATHS_N, + &[ + DISJOINT_PATHS_N[0], + DISJOINT_PATHS_N[1], + DISJOINT_PATHS_N[2], + DISJOINT_PATHS_N[3], + DISJOINT_PATHS_N[4], + DISJOINT_PATHS_N[5], + ] + .concat(), + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_xor_n!(z0, z1, z2, z3, z4, z5 => out), + ); + } + + #[test] + fn test_deep_shared_prefix_then_split() { + check2( + &PATHS_WITH_SHARED_PREFIX, + &[PATHS_WITH_SHARED_PREFIX.0, PATHS_WITH_SHARED_PREFIX.1].concat(), + |mut lhs, mut rhs, out| lhs.xor(&mut rhs, out), + ); + } + + #[test] + fn test_deep_shared_prefix_then_split3() { + check3( + &PATHS_WITH_SHARED_PREFIX_3, + &[ + PATHS_WITH_SHARED_PREFIX_3.0, + PATHS_WITH_SHARED_PREFIX_3.1, + PATHS_WITH_SHARED_PREFIX_3.2, + ] + .concat(), + |mut lhs, mut mid, mut rhs, out| zipper_xor3(&mut lhs, &mut mid, &mut rhs, out), + ); + } + + #[test] + fn test_deep_shared_prefix_then_split_n() { + checkn( + &PATHS_WITH_SHARED_PREFIX_N, + &[ + PATHS_WITH_SHARED_PREFIX_N[0], + PATHS_WITH_SHARED_PREFIX_N[1], + PATHS_WITH_SHARED_PREFIX_N[2], + PATHS_WITH_SHARED_PREFIX_N[3], + PATHS_WITH_SHARED_PREFIX_N[4], + PATHS_WITH_SHARED_PREFIX_N[5], + ] + .concat(), + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_xor_n!(z0, z1, z2, z3, z4, z5 => out), + ); + } + + #[test] + fn test_interleaving_paths() { + check2( + &INTERLEAVING_PATHS, + &[INTERLEAVING_PATHS.0, INTERLEAVING_PATHS.1].concat(), + |mut lhs, mut rhs, out| lhs.xor(&mut rhs, out), ); } @@ -3586,10 +4683,13 @@ mod tests { fn test_interleaving_paths3() { check3( &INTERLEAVING_PATHS_3, - INTERLEAVING_PATHS_3.0, - |lhs, mid, rhs, out| { - zipper_subtract3(lhs, mid, rhs, out); - }, + &[ + INTERLEAVING_PATHS_3.0, + INTERLEAVING_PATHS_3.1, + INTERLEAVING_PATHS_3.2, + ] + .concat(), + |mut lhs, mut mid, mut rhs, out| zipper_xor3(&mut lhs, &mut mid, &mut rhs, out), ); } @@ -3597,8 +4697,16 @@ mod tests { fn test_interleaving_paths_n() { checkn( &INTERLEAVING_PATHS_N, - INTERLEAVING_PATHS_N[0], - |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + &[ + INTERLEAVING_PATHS_N[0], + INTERLEAVING_PATHS_N[1], + INTERLEAVING_PATHS_N[2], + INTERLEAVING_PATHS_N[3], + INTERLEAVING_PATHS_N[4], + INTERLEAVING_PATHS_N[5], + ] + .concat(), + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_xor_n!(z0, z1, z2, z3, z4, z5 => out), ); } @@ -3607,145 +4715,202 @@ mod tests { let expected: Paths = &[ (&[0x00, 0x01], 1), (&[0x00, 0x01, 0x02], 2), - (&[0x00, 0x01, 0x02, 0x03], 3), (&[0x01], 4), (&[0x01, 0x02], 5), (&[0x01, 0x02, 0x03], 6), (&[0x01, 0x02, 0x03, 0x04], 7), - (&[0x01, 0x02, 0x03, 0x04, 0x05], 8), (&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06], 9), ]; - check2(&ONE_SIDED_PATHS, expected, |lhs, rhs, out| { - lhs.subtract(rhs, out) + check2(&ONE_SIDED_PATHS, expected, |mut lhs, mut rhs, out| { + lhs.xor(&mut rhs, out) }); } #[test] fn test_one_side_empty_at_many_levels3() { let expected: Paths = &[ + (&[0x00], 0), (&[0x00, 0x01], 1), - (&[0x00, 0x01, 0x02], 2), - (&[0x00, 0x01, 0x02, 0x03], 3), (&[0x01], 4), (&[0x01, 0x02], 5), (&[0x01, 0x02, 0x03], 6), (&[0x01, 0x02, 0x03, 0x04], 7), - (&[0x01, 0x02, 0x03, 0x04, 0x05], 8), (&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06], 9), ]; - check3(&ONE_SIDED_PATHS_3, expected, |lhs, mid, rhs, out| { - zipper_subtract3(lhs, mid, rhs, out); - }); + check3( + &ONE_SIDED_PATHS_3, + expected, + |mut lhs, mut mid, mut rhs, out| zipper_xor3(&mut lhs, &mut mid, &mut rhs, out), + ); } #[test] fn test_one_side_empty_at_many_levels_n() { let expected: Paths = &[ + (&[0x00], 0), (&[0x00, 0x01], 1), - (&[0x00, 0x01, 0x02], 2), - (&[0x00, 0x01, 0x02, 0x03], 3), - (&[0x01, 0x02, 0x03], 6), (&[0x01, 0x02, 0x03, 0x04], 7), - (&[0x01, 0x02, 0x03, 0x04, 0x05], 8), (&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06], 9), ]; checkn( &ONE_SIDED_PATHS_N, expected, - |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_xor_n!(z0, z1, z2, z3, z4, z5 => out), ); } #[test] fn test_almost_identical_paths() { let expected: Paths = &[(b"hijklmnop", 1), (b"2", 5), (b"3", 6)]; - check2(&ALMOST_IDENTICAL_PATHS, expected, |lhs, rhs, out| { - lhs.subtract(rhs, out) - }); + check2( + &ALMOST_IDENTICAL_PATHS, + expected, + |mut lhs, mut rhs, out| lhs.xor(&mut rhs, out), + ); } #[test] fn test_almost_identical_paths3() { - check3(&ALMOST_IDENTICAL_PATHS_3, [], |lhs, mid, rhs, out| { - zipper_subtract3(lhs, mid, rhs, out); - }); + let expected: Paths = &[(b"abcdefg", 0), (b"1", 4), (b"4", 7), (b"5", 8)]; + check3( + &ALMOST_IDENTICAL_PATHS_3, + expected, + |mut lhs, mut mid, mut rhs, out| zipper_xor3(&mut lhs, &mut mid, &mut rhs, out), + ); } #[test] fn test_almost_identical_paths_n() { + let expected: Paths = &[(b"abcdefg", 0), (b"hijklmnop", 1), (b"2", 5), (b"3", 6)]; checkn( &ALMOST_IDENTICAL_PATHS_N, - [], - |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + expected, + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_xor_n!(z0, z1, z2, z3, z4, z5 => out), ); } #[test] fn test_one_side_empty() { - check2(&LHS_EMPTY, [], |lhs, rhs, out| lhs.subtract(rhs, out)); - check2(&RHS_EMPTY, RHS_EMPTY.0, |lhs, rhs, out| { - lhs.subtract(rhs, out) + check2(&LHS_EMPTY, LHS_EMPTY.1, |mut lhs, mut rhs, out| { + lhs.xor(&mut rhs, out) + }); + check2(&RHS_EMPTY, RHS_EMPTY.0, |mut lhs, mut rhs, out| { + lhs.xor(&mut rhs, out) }); } #[test] fn test_one_side_empty3() { - check3(&LHS_EMPTY_3, [], |lhs, mid, rhs, out| { - zipper_subtract3(lhs, mid, rhs, out); - }); - check3(&MID_EMPTY, MID_EMPTY.0, |lhs, mid, rhs, out| { - zipper_subtract3(lhs, mid, rhs, out); - }); - check3(&RHS_EMPTY_3, RHS_EMPTY_3.0, |lhs, mid, rhs, out| { - zipper_subtract3(lhs, mid, rhs, out); - }); + check3( + &LHS_EMPTY_3, + &[LHS_EMPTY_3.1, LHS_EMPTY_3.2].concat(), + |mut lhs, mut mid, mut rhs, out| zipper_xor3(&mut lhs, &mut mid, &mut rhs, out), + ); + check3( + &MID_EMPTY, + &[MID_EMPTY.0, MID_EMPTY.2].concat(), + |mut lhs, mut mid, mut rhs, out| zipper_xor3(&mut lhs, &mut mid, &mut rhs, out), + ); + check3( + &RHS_EMPTY_3, + &[RHS_EMPTY_3.0, RHS_EMPTY_3.1].concat(), + |mut lhs, mut mid, mut rhs, out| zipper_xor3(&mut lhs, &mut mid, &mut rhs, out), + ); } #[test] fn test_one_side_empty_n() { checkn( &LHS_EMPTY_N, - [], - |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + &[ + LHS_EMPTY_N[1], + LHS_EMPTY_N[2], + LHS_EMPTY_N[3], + LHS_EMPTY_N[4], + LHS_EMPTY_N[5], + ] + .concat(), + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_xor_n!(z0, z1, z2, z3, z4, z5 => out), ); checkn( &MID_EMPTY_N, - MID_EMPTY_N[0], - |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + &[ + MID_EMPTY_N[0], + MID_EMPTY_N[1], + MID_EMPTY_N[3], + MID_EMPTY_N[4], + MID_EMPTY_N[5], + ] + .concat(), + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_xor_n!(z0, z1, z2, z3, z4, z5 => out), ); checkn( &RHS_EMPTY_N, - RHS_EMPTY_N[0], - |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + &[ + RHS_EMPTY_N[0], + RHS_EMPTY_N[1], + RHS_EMPTY_N[2], + RHS_EMPTY_N[3], + RHS_EMPTY_N[4], + ] + .concat(), + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_xor_n!(z0, z1, z2, z3, z4, z5 => out), ); } #[test] fn test_exact_overlap_divergent_subtries() { + let expected: Paths = &[ + (&[1, 2, 3, 4], 1), + (&[1, 2, 3, 5], 11), + (&[1, 2, 3, 10, 11, 0], 12), + (&[1, 2, 3, 10, 11, 12], 2), + ]; check2( &PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN, - PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN.0, - |lhs, rhs, out| lhs.subtract(rhs, out), + expected, + |mut lhs, mut rhs, out| lhs.xor(&mut rhs, out), ); } #[test] fn test_exact_overlap_divergent_subtries3() { + let expected: Paths = &[ + (&[1, 2, 3], 20), + (&[1, 2, 3, 4], 1), + (&[1, 2, 3, 5], 11), + (&[1, 2, 3, 6], 21), + (&[1, 2, 3, 10, 11, 0], 12), + (&[1, 2, 3, 10, 11, 1], 22), + (&[1, 2, 3, 10, 11, 12], 2), + ]; check3( &PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN_3, - PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN_3.0, - |lhs, mid, rhs, out| { - zipper_subtract3(lhs, mid, rhs, out); - }, + expected, + |mut lhs, mut mid, mut rhs, out| zipper_xor3(&mut lhs, &mut mid, &mut rhs, out), ); } #[test] fn test_exact_overlap_divergent_subtries_n() { + let expected: Paths = &[ + (&[1, 2, 3, 4], 1), + (&[1, 2, 3, 5], 11), + (&[1, 2, 3, 6], 21), + (&[1, 2, 3, 7], 31), + (&[1, 2, 3, 8], 41), + (&[1, 2, 3, 9], 51), + (&[1, 2, 3, 10, 11, 0], 12), + (&[1, 2, 3, 10, 11, 1], 22), + (&[1, 2, 3, 10, 11, 2], 32), + (&[1, 2, 3, 10, 11, 3], 42), + (&[1, 2, 3, 10, 11, 4], 52), + (&[1, 2, 3, 10, 11, 12], 2), + ]; checkn( &PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN_N, - PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN_N[0], - |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + expected, + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_xor_n!(z0, z1, z2, z3, z4, z5 => out), ); } @@ -3757,26 +4922,42 @@ mod tests { (&[3, 2, 1], 4), (&[4], 4), (&[4, 3, 2, 1], 5), + (&[1], 0), + (&[1, 2], 1), + (&[3, 4], 4), + (&[4, 3], 5), ]; - check2(&ZIGZAG_PATHS, expected, |lhs, rhs, out| { - lhs.subtract(rhs, out) + check2(&ZIGZAG_PATHS, expected, |mut lhs, mut rhs, out| { + lhs.xor(&mut rhs, out) }); } #[test] fn test_zigzag3() { - let expected: Paths = &[(&[1, 1], 0), (&[3, 2, 1], 4), (&[4], 4)]; - check3(&ZIGZAG_PATHS_3, expected, |lhs, mid, rhs, out| { - zipper_subtract3(lhs, mid, rhs, out); - }); + let expected: Paths = &[ + (&[1, 1], 0), + (&[2, 1], 2), + (&[3, 2, 1], 4), + (&[4], 4), + (&[1, 2], 1), + (&[3, 4], 4), + (&[4, 3], 5), + (&[3, 2, 1, 0], 3), + (&[4, 3, 2, 1, 0], 4), + ]; + check3( + &ZIGZAG_PATHS_3, + expected, + |mut lhs, mut mid, mut rhs, out| zipper_xor3(&mut lhs, &mut mid, &mut rhs, out), + ); } #[test] fn test_root_values() { check2( &PATHS_WITH_ROOT_VALS_AND_CHILDREN, - PATHS_WITH_ROOT_VALS_AND_CHILDREN.0, - |lhs, rhs, out| lhs.subtract(rhs, out), + &[], + |mut lhs, mut rhs, out| lhs.xor(&mut rhs, out), ); } @@ -3784,10 +4965,8 @@ mod tests { fn test_root_values3() { check3( &PATHS_WITH_ROOT_VALS_AND_CHILDREN_3, - PATHS_WITH_ROOT_VALS_AND_CHILDREN_3.0, - |lhs, mid, rhs, out| { - zipper_subtract3(lhs, mid, rhs, out); - }, + PATHS_WITH_ROOT_VALS_AND_CHILDREN_3.2, + |mut lhs, mut mid, mut rhs, out| zipper_xor3(&mut lhs, &mut mid, &mut rhs, out), ); } @@ -3795,8 +4974,8 @@ mod tests { fn test_root_values_n() { checkn( &PATHS_WITH_ROOT_VALS_AND_CHILDREN_N, - PATHS_WITH_ROOT_VALS_AND_CHILDREN_N[0], - |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_subtract_n!(z0, z1, z2, z3, z4, z5 => out), + &[], + |[mut z0, mut z1, mut z2, mut z3, mut z4, mut z5], mut out| zipper_xor_n!(z0, z1, z2, z3, z4, z5 => out), ); } } @@ -3925,7 +5104,9 @@ mod tests { } mod dnf { - use crate::experimental::zipper_algebra::{zipper_join3, zipper_meet3, zipper_merge_dnf}; + use crate::experimental::zipper_algebra::{ + Clause, zipper_join3, zipper_meet3, zipper_merge_dnf, + }; use super::*; @@ -3945,7 +5126,7 @@ mod tests { let mut result = PathMap::new(); let mut out = result.write_zipper(); - zipper_merge_dnf(&mut [&mut [&mut z1, &mut z2, &mut z3]], &mut out); + zipper_merge_dnf(&mut [&mut z1, &mut z2, &mut z3], [Clause::FULL], &mut out); let mut expected = PathMap::new(); { @@ -3971,7 +5152,12 @@ mod tests { let mut result = PathMap::new(); let mut out = result.write_zipper(); zipper_merge_dnf( - &mut [&mut [&mut z1], &mut [&mut z2], &mut [&mut z3]], + &mut [&mut z1, &mut z2, &mut z3], + [ + Clause::singleton(0), + Clause::singleton(1), + Clause::singleton(2), + ], &mut out, ); @@ -3992,16 +5178,15 @@ mod tests { let mut trie2 = PathMap::from_iter(SMALL_TRIE_2); let mut trie3 = PathMap::from_iter(SMALL_TRIE_3); + let mut z1 = trie1.read_zipper(); let mut z2 = trie2.read_zipper(); let mut z3 = trie3.read_zipper(); let mut result = PathMap::new(); let mut out = result.write_zipper(); zipper_merge_dnf( - &mut [ - &mut [&mut trie1.read_zipper(), &mut z2], - &mut [&mut trie1.read_zipper(), &mut z3], - ], + &mut [z1, z2, z3], + [Clause::from_mask(0b011), Clause::from_mask(0b101)], &mut out, ); let expected = trie1.meet(&trie2.join(&trie3)); @@ -4021,10 +5206,8 @@ mod tests { let mut result = PathMap::new(); let mut out = result.write_zipper(); zipper_merge_dnf( - &mut [ - &mut [&mut z1, &mut trie2.read_zipper()], - &mut [&mut trie2.read_zipper(), &mut z3], - ], + &mut [z1, z2, z3], + [Clause::from_mask(0b011), Clause::from_mask(0b110)], &mut out, ); let expected = trie2.meet(&trie1.join(&trie3)); @@ -4037,15 +5220,15 @@ mod tests { let mut trie2 = PathMap::from_iter(SMALL_TRIE_2); let mut trie3 = PathMap::from_iter(SMALL_TRIE_3); + let mut z1 = trie1.read_zipper(); + let mut z2 = trie2.read_zipper(); let mut z3 = trie3.read_zipper(); let mut result = PathMap::new(); let mut out = result.write_zipper(); zipper_merge_dnf( - &mut [ - &mut [&mut trie1.read_zipper(), &mut trie2.read_zipper(), &mut z3], - &mut [&mut trie1.read_zipper(), &mut trie2.read_zipper()], - ], + &mut [z1, z2, z3], + [Clause::FULL, Clause::from_indices([0, 1])], &mut out, ); let expected = trie2.meet(&trie1); @@ -4082,15 +5265,18 @@ mod tests { let a_shallow_chain = prefixed(&trie2.read_zipper(), a); let a_branching = prefixed(&trie3.read_zipper(), a); - let mut z3 = trie3.read_zipper(); - let mut result = PathMap::new(); let mut out = result.write_zipper(); zipper_merge_dnf( &mut [ - &mut [&mut a_deep_chain.read_zipper()], - &mut [&mut a_shallow_chain.read_zipper()], - &mut [&mut a_branching.read_zipper()], + a_deep_chain.read_zipper(), + a_shallow_chain.read_zipper(), + a_branching.read_zipper(), + ], + [ + Clause::singleton(0), + Clause::singleton(1), + Clause::singleton(2), ], &mut out, ); @@ -4098,4 +5284,100 @@ mod tests { assert_trie(expected, result); } } + + mod maj { + use super::*; + use crate::experimental::zipper_algebra::zipper_majority; + + #[test] + fn test_disjoint() { + check3(&DISJOINT_PATHS_3, [], |lhs, mid, rhs, out| { + zipper_majority(lhs, mid, rhs, out); + }); + } + + #[test] + fn test_deep_shared_prefix_then_split() { + check3(&PATHS_WITH_SHARED_PREFIX_3, [], |lhs, mid, rhs, out| { + zipper_majority(lhs, mid, rhs, out); + }); + } + + #[test] + fn test_interleaving_paths() { + check3(&INTERLEAVING_PATHS_3, [], |lhs, mid, rhs, out| { + zipper_majority(lhs, mid, rhs, out); + }); + } + + #[test] + fn test_one_side_empty_at_many_levels() { + let expected: Paths = &[ + (&[0x00], 0), + (&[0x00, 0x01, 0x02], 2), + (&[0x00, 0x01, 0x02, 0x03], 3), + (&[0x01, 0x02, 0x03, 0x04, 0x05], 8), + ]; + check3(&ONE_SIDED_PATHS_3, expected, |lhs, mid, rhs, out| { + zipper_majority(lhs, mid, rhs, out); + }); + } + + #[test] + fn test_almost_identical_paths() { + check3( + &ALMOST_IDENTICAL_PATHS_3, + ALMOST_IDENTICAL_PATHS_3.0, + |lhs, mid, rhs, out| { + zipper_majority(lhs, mid, rhs, out); + }, + ); + } + + #[test] + fn test_one_side_empty() { + check3(&LHS_EMPTY_3, [], |lhs, mid, rhs, out| { + zipper_majority(lhs, mid, rhs, out) + }); + check3(&MID_EMPTY, [], |lhs, mid, rhs, out| { + zipper_majority(lhs, mid, rhs, out) + }); + check3(&RHS_EMPTY_3, [], |lhs, mid, rhs, out| { + zipper_majority(lhs, mid, rhs, out) + }); + } + + #[test] + fn test_exact_overlap_divergent_subtries() { + let expected: Paths = &[(&[1, 2, 3], 0)]; + check3( + &PATHS_WITH_SAME_PREFIX_DIFFERENT_CHILDREN_3, + expected, + |lhs, mid, rhs, out| zipper_majority(lhs, mid, rhs, out), + ); + } + + #[test] + fn test_zigzag() { + let expected: Paths = &[ + (&[2, 1], 2), + (&[1], 0), + (&[2], 1), + (&[3], 3), + (&[4, 3, 2, 1], 5), + ]; + check3(&ZIGZAG_PATHS_3, expected, |lhs, mid, rhs, out| { + zipper_majority(lhs, mid, rhs, out) + }); + } + + #[test] + fn test_root_values() { + check3( + &PATHS_WITH_ROOT_VALS_AND_CHILDREN_3, + PATHS_WITH_ROOT_VALS_AND_CHILDREN.0, + |lhs, mid, rhs, out| zipper_majority(lhs, mid, rhs, out), + ); + } + } }