aph_disjoint_set 0.1.1

Disjoint set implementation with optimized memory usage and ability to detach elements.
Documentation
use crate::macros::{common_strct, for_current_bitness};
use crate::storage::{Storage, ViewStorage};

use super::{Root, UnionResult};

/// Mutable view to part of bigger disjoint set.
/// Useful for running union operations using multiple threads.
/// Contains nodes in range `lower_bound()..upper_bound()`.
pub struct ChunkMut<'owner>(pub(crate) common_strct!(ViewStorage, 'owner));

impl ChunkMut<'_> {
    /// Split view into 2 non-overlapping parts at node with index.
    /// Index is relative to owning disjoint set.
    /// # Panics
    /// If index is smaller than lower bound or greater or equal to upper bound.
    #[must_use]
    pub fn split_at(mut self, split_idx: usize) -> (Self, Self) {
        unsafe {
            // SAFETY: Returned lifetime is same as our lifetime.
            // And our lifetime cannot ourlive owner.
            self.0.split_at(split_idx)
        }
    }

    /// Minimal value of node index allowed to be accessed using this chunk.
    #[inline]
    #[must_use]
    pub fn lower_bound(&self) -> usize {
        for_current_bitness!(&self.0.get_storage(); s; s.lower_bound())
    }

    /// Upper limit (exclusive) to node index allowed to be accessed using this chunk.
    #[inline]
    #[must_use]
    pub fn upper_bound(&self) -> usize {
        for_current_bitness!(&self.0.get_storage(); s; s.upper_bound())
    }

    /// Returns tag associated with node at index.
    /// # Panics
    /// If index less than lower bound or greater or equal to upper bound.
    #[must_use]
    pub fn get_root(&mut self, idx: usize) -> Root {
        self.0.get_root(idx)
    }

    /// Performs union operation for subsets that contain nodes `idx0` and `idx1`.
    /// Note that for mutable chunks this operation limited to bounds of chunk.
    /// After calling this method, further splitting of chunk is not possible.
    /// # Panics
    /// If either `idx0` or `idx1` less than lower bound or greater or equal to upper bound.
    pub fn union(&mut self, idx0: usize, idx1: usize) -> UnionResult {
        self.0.union(idx0, idx1)
    }

    /// Same as [`DisjointSet::is_united`](crate::DisjointSet::is_united).
    /// # Panics
    /// If either `idx0` or `idx1` less than lower bound or greater or equal to upper bound.
    #[must_use]
    pub fn is_united(&mut self, idx0: usize, idx1: usize) -> bool {
        self.0.is_united(idx0, idx1)
    }

    /// Same as [`DisjointSet::detach`](crate::DisjointSet::detach).
    /// # Panics
    /// If index is out of bounds.
    pub fn detach(&mut self, idx: usize) {
        self.0.detach(idx);
    }

    /// Eagerly apply path compression optimization.
    /// Has complexity _O(n)_.
    /// It compress paths only in current chunk.
    /// This can be useful because if you call this for different chunks in separate threads,
    /// most operations on owning disjoint set would be faster.
    pub fn compress_paths(&mut self) {
        self.0.compress_paths();
    }
}

#[allow(clippy::let_underscore_untyped)]
#[cfg(test)]
mod tests {
    use rstest::rstest;

    use crate::{DisjointSet, Root};

    #[test]
    fn test_split() {
        let mut djs = DisjointSet::new(10);
        let (mut first, rest) = djs.split_at(4);
        let (mut second, mut third) = rest.split_at(8);

        assert_eq!(first.lower_bound(), 0);
        assert_eq!(first.upper_bound(), 4);
        assert_eq!(second.lower_bound(), 4);
        assert_eq!(second.upper_bound(), 8);
        assert_eq!(third.lower_bound(), 8);
        assert_eq!(third.upper_bound(), 10);

        std::thread::scope(|s| {
            s.spawn(|| first.union(2, 3));
            s.spawn(|| second.union(5, 6));
            s.spawn(|| third.union(8, 9));
        });
        assert_eq!(second.get_root(6), Root(5));
        assert_eq!(third.get_root(9), Root(8));
        assert_eq!(first.get_root(3), Root(2));

        assert!(djs.is_united(2, 3));
        assert!(djs.is_united(5, 6));
        assert!(djs.is_united(8, 9));
    }

    #[rstest]
    #[should_panic]
    fn test_split_bounds_check(#[values(1, 3, 9, 10, usize::MAX)] idx: usize) {
        let mut djs = DisjointSet::new(8);
        let (_, right) = djs.split_at(4);
        let _: (_, _) = right.split_at(idx);
    }

    #[rstest]
    #[should_panic]
    fn test_get_root_bounds_check(#[values(1, 3, 9, 10, usize::MAX)] idx: usize) {
        let mut djs = DisjointSet::new(8);
        let (_, mut right) = djs.split_at(4);
        let _: Root = right.get_root(idx);
    }

    #[rstest]
    #[case(0, 8)]
    #[case(0, 9)]
    #[case(8, 0)]
    #[case(9, 0)]
    #[case(8, 9)]
    #[case(9, 8)]
    #[case(1, 5)]
    #[should_panic]
    fn test_union_bounds_check(#[case] idx0: usize, #[case] idx1: usize) {
        let mut djs = DisjointSet::new(8);
        let (_, mut right) = djs.split_at(4);
        let _ = right.union(idx0, idx1);
    }
}