From d6a8bcd071300bda18fa323d8fecf34e5ed83ca8 Mon Sep 17 00:00:00 2001 From: Aziz Kayumov Date: Wed, 4 Oct 2023 14:59:35 +0900 Subject: [PATCH 1/2] connectivity random test #12 --- Cargo.toml | 1 + src/lib.rs | 1 - tests/test_connectivity.rs | 112 +++++++++++++++++++++++++++++++++++ tests/test_findmax_random.rs | 40 ++++++++----- 4 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 tests/test_connectivity.rs diff --git a/Cargo.toml b/Cargo.toml index 0c4332c..f1e6431 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ edition = "2021" [dev-dependencies] rand = "0.8" +rand_derive2 = "0.1.21" diff --git a/src/lib.rs b/src/lib.rs index 8e2a384..1c3f4fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,7 +51,6 @@ impl LinkCutTree { } /// Creates a link between two nodes in the forest (v becomes the parent of w) - /// # Panics if v has a left child (i.e. v is not a root node) pub fn link(&mut self, v: usize, w: usize) { self.access(v); self.access(w); diff --git a/tests/test_connectivity.rs b/tests/test_connectivity.rs new file mode 100644 index 0000000..0467283 --- /dev/null +++ b/tests/test_connectivity.rs @@ -0,0 +1,112 @@ +use lctree::LinkCutTree; +use rand::{rngs::StdRng, seq::IteratorRandom, Rng, SeedableRng}; +use rand_derive2::RandGen; +use std::collections::HashSet; + +fn create_random_generator() -> StdRng { + let seed = rand::thread_rng().gen(); + println!("Seed: {}", seed); // print seed so we can reproduce the test (if it fails). + StdRng::seed_from_u64(seed) +} + +fn create_random_tree(rng: &mut StdRng) -> Vec<(usize, usize)> { + let mut nodes = Vec::from([0]); + let mut edges = Vec::new(); + for i in 1..NUMBER_OF_NODES { + let parent = nodes[rng.gen_range(0..i)]; + nodes.push(i); + edges.push((i, parent)); + } + edges +} + +fn dfs( + v: usize, + adj: &Vec>, + visited: &mut Vec, + component_ids: &mut Vec, + component_id: usize, +) { + visited[v] = true; + component_ids[v] = component_id; + for &w in &adj[v] { + if !visited[w] { + dfs(w, adj, visited, component_ids, component_id); + } + } +} + +fn connected_components(edges: &HashSet<(usize, usize)>) -> Vec { + // create adjacency list from edges + let mut adj = vec![vec![]; NUMBER_OF_NODES]; + for (v, w) in edges { + adj[*v].push(*w); + adj[*w].push(*v); + } + + // explore each component using dfs and assign component ids + let mut visited = vec![false; NUMBER_OF_NODES]; + let mut component_ids = vec![0; NUMBER_OF_NODES]; + let mut component_id = 0; + for v in 0..NUMBER_OF_NODES { + if !visited[v] { + component_id += 1; + dfs(v, &adj, &mut visited, &mut component_ids, component_id); + } + } + component_ids +} + +const NUMBER_OF_NODES: usize = 100; +const NUMBER_OF_OPERATIONS: usize = 1000; + +#[derive(RandGen)] +enum Operation { + Link, + Cut, + Connected, +} + +#[test] +pub fn test_connectivity() { + let mut rng = create_random_generator(); + let edges = create_random_tree(&mut rng); + + // initialize link-cut tree, we start with a forest of single nodes + // (edges are not added yet): + let mut lctree = LinkCutTree::new(NUMBER_OF_NODES); + let mut edges_in_tree = HashSet::new(); + let mut component_ids = (0..NUMBER_OF_NODES).collect::>(); + + // perform random operations: link, cut, or connected: + for _ in 0..NUMBER_OF_OPERATIONS { + let operation: Operation = rng.gen(); + match operation { + Operation::Link => { + let (v, w) = edges.iter().choose(&mut rng).unwrap(); + println!("Link {} {}", v, w); + lctree.link(*v, *w); + + edges_in_tree.insert((*v, *w)); + component_ids = connected_components(&edges_in_tree); + } + Operation::Cut => { + if edges_in_tree.is_empty() { + continue; + } + let (v, w) = edges_in_tree.iter().choose(&mut rng).unwrap(); + println!("Cut {} {}", v, w); + lctree.cut(*v); + + edges_in_tree.remove(&(*v, *w)); + component_ids = connected_components(&edges_in_tree); + } + Operation::Connected => { + let v = rng.gen_range(0..NUMBER_OF_NODES); + let w = rng.gen_range(0..NUMBER_OF_NODES); + println!("Connected {} {}", v, w); + assert_eq!(lctree.connected(v, w), component_ids[v] == component_ids[w]); + } + } + } +} diff --git a/tests/test_findmax_random.rs b/tests/test_findmax_random.rs index efeab71..d3f7f2e 100644 --- a/tests/test_findmax_random.rs +++ b/tests/test_findmax_random.rs @@ -1,16 +1,20 @@ use lctree::LinkCutTree; -use rand::{seq::SliceRandom, Rng, SeedableRng}; +use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng}; -fn create_random_tree(n: usize, seed: u64) -> (Vec<(usize, usize)>, Vec, Vec) { - let mut rng = rand::rngs::StdRng::seed_from_u64(seed); +fn create_random_generator() -> StdRng { + let seed = rand::thread_rng().gen(); + println!("Seed: {}", seed); // print seed so we can reproduce the test (if it fails). + StdRng::seed_from_u64(seed) +} +fn create_random_tree(rng: &mut StdRng) -> (Vec<(usize, usize)>, Vec, Vec) { let mut edges = Vec::new(); - let mut weights: Vec = (0..n).map(|i| i as f64).collect(); - weights.shuffle(&mut rng); + let mut weights: Vec = (0..NUMBER_OF_NODES).map(|i| i as f64).collect(); + weights.shuffle(rng); - let mut max_to_root = vec![0; n]; + let mut max_to_root = vec![0; NUMBER_OF_NODES]; let mut in_tree = Vec::from([0]); - for i in 1..n { + for i in 1..NUMBER_OF_NODES { let parent_idx = rng.gen_range(0..in_tree.len()); let parent = in_tree[parent_idx]; edges.push((i, parent)); @@ -26,22 +30,26 @@ fn create_random_tree(n: usize, seed: u64) -> (Vec<(usize, usize)>, Vec, Ve (edges, weights, max_to_root) } +const NUMBER_OF_NODES: usize = 1000; + #[test] pub fn findmax_random() { - let n = 100; - let seed = rand::thread_rng().gen(); - let (edges, weights, max_to_root) = create_random_tree(n, seed); - let mut lctree = LinkCutTree::new(n); - for i in 0..n { + let mut rng = create_random_generator(); + let (edges, weights, expected_findmax) = create_random_tree(&mut rng); + + // initialize link-cut tree + let mut lctree = LinkCutTree::new(NUMBER_OF_NODES); + for i in 0..NUMBER_OF_NODES { lctree.set_weight(i, weights[i]); } - for (v, w) in edges { lctree.link(v, w); } - for _ in 0..n * 100 { - let v = rand::thread_rng().gen_range(0..n); - assert_eq!(lctree.findmax(v), max_to_root[v]); + // perform random findmax queries + let mut nodes = (0..NUMBER_OF_NODES).collect::>(); + nodes.shuffle(&mut rng); + for v in nodes { + assert_eq!(lctree.findmax(v), expected_findmax[v]); } } From 9d2e59d60b99801d8f691af9b8ae2ce91233acfc Mon Sep 17 00:00:00 2001 From: Aziz Kayumov Date: Wed, 4 Oct 2023 15:03:28 +0900 Subject: [PATCH 2/2] rename findmax --- tests/{test_findmax_random.rs => test_findmax.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{test_findmax_random.rs => test_findmax.rs} (100%) diff --git a/tests/test_findmax_random.rs b/tests/test_findmax.rs similarity index 100% rename from tests/test_findmax_random.rs rename to tests/test_findmax.rs