use yo_common::Rng;
use crate::Snapshot;
use crate::algo::Components;
const ROUNDS: usize = 2;
const SAMPLE: usize = 1024;
const SEED: u64 = 0x00c0_ffee;
#[must_use]
pub fn wcc(g: &Snapshot) -> Components {
let n = g.nodes() as usize;
let mut of: Vec<u32> = (0..n as u32).collect();
if n == 0 {
return Components { of, count: 0 };
}
for r in 0..ROUNDS {
for node in 0..n as u32 {
if let Some(to) = g.out(node).get(r) {
link(&mut of, node, *to);
}
}
compress(&mut of);
}
let big = frequent(&of);
for node in 0..n as u32 {
if of[node as usize] == big {
continue;
}
for to in g.out(node).iter().skip(ROUNDS) {
link(&mut of, node, *to);
}
for from in g.into_(node) {
link(&mut of, node, *from);
}
}
compress(&mut of);
let count = of
.iter()
.enumerate()
.filter(|(at, c)| **c == *at as u32)
.count() as u32;
Components { of, count }
}
fn link(of: &mut [u32], a: u32, b: u32) {
let (mut p1, mut p2) = (of[a as usize], of[b as usize]);
while p1 != p2 {
let (high, low) = if p1 > p2 { (p1, p2) } else { (p2, p1) };
let up = of[high as usize];
if up == low {
break;
}
if up == high {
of[high as usize] = low;
break;
}
p1 = of[up as usize];
p2 = of[low as usize];
}
}
fn compress(of: &mut [u32]) {
for node in 0..of.len() {
while of[node] != of[of[node] as usize] {
of[node] = of[of[node] as usize];
}
}
}
fn frequent(of: &[u32]) -> u32 {
let mut rng = Rng::new(SEED);
let mut seen: Vec<(u32, u32)> = Vec::with_capacity(SAMPLE);
for _ in 0..SAMPLE.min(of.len()) {
let label = of[rng.below(of.len())];
match seen.iter_mut().find(|(l, _)| *l == label) {
Some((_, n)) => *n += 1,
None => seen.push((label, 1)),
}
}
seen.iter()
.max_by_key(|(_, n)| *n)
.map_or(0, |(label, _)| *label)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::NO_PROPS;
use crate::{Graph, Snapshot};
use yo_common::Rng;
fn reference(g: &Snapshot) -> Vec<u32> {
let mut of: Vec<u32> = (0..g.nodes()).collect();
fn root(of: &mut [u32], mut at: u32) -> u32 {
while of[at as usize] != at {
at = of[at as usize];
}
at
}
for node in 0..g.nodes() {
for to in g.out(node) {
let (a, b) = (root(&mut of, node), root(&mut of, *to));
let (high, low) = if a > b { (a, b) } else { (b, a) };
of[high as usize] = low;
}
}
for node in 0..g.nodes() {
let r = root(&mut of, node);
of[node as usize] = r;
}
of
}
fn linked(edges: &[(u64, u64)]) -> Graph {
let mut g = Graph::new();
for (src, dst) in edges {
g.link(*src, *dst, 1, NO_PROPS).unwrap();
}
g
}
#[test]
fn two_pieces_are_two_components() {
let s = Snapshot::of(&linked(&[(0, 1), (1, 2), (5, 6)]));
let c = wcc(&s);
assert_eq!(c.count(), 2);
assert_eq!(c.len(), 5);
assert!(c.same(0, 2), "one piece");
assert!(!c.same(0, 3), "the other");
assert_eq!(c.of(0), 0, "named by the lowest node in it");
assert_eq!(c.largest(), Some((0, 3)));
}
#[test]
fn an_edge_joins_both_of_its_ends_whichever_way_it_points() {
let s = Snapshot::of(&linked(&[(0, 1), (2, 1)]));
let c = wcc(&s);
assert_eq!(c.count(), 1);
assert!(c.same(0, 2));
}
#[test]
fn a_node_with_no_edges_is_its_own_component() {
let mut g = Graph::new();
g.link(0, 1, 1, NO_PROPS).unwrap();
g.add_node(9).unwrap();
let s = Snapshot::of(&g);
let c = wcc(&s);
assert_eq!(c.count(), 2);
assert_eq!(c.largest(), Some((0, 2)));
assert!(!c.same(0, s.dense(9).unwrap()));
}
#[test]
fn nothing_at_all_has_no_components() {
let c = wcc(&Snapshot::of(&Graph::new()));
assert!(c.is_empty());
assert_eq!(c.count(), 0);
assert_eq!(c.largest(), None);
}
#[test]
fn a_self_loop_and_a_parallel_edge_change_nothing() {
let s = Snapshot::of(&linked(&[(0, 0), (0, 1), (0, 1), (1, 1)]));
let c = wcc(&s);
assert_eq!(c.count(), 1);
assert_eq!(c.labels(), [0, 0]);
}
#[test]
fn it_agrees_with_union_find_on_a_hundred_random_graphs() {
let mut rng = Rng::new(0xbeef);
let (trials, nodes, edges) = if cfg!(miri) {
(3, 8, 12)
} else {
(100, 80, 120)
};
for trial in 0..trials {
let n = 1 + rng.below(nodes) as u64;
let m = rng.below(edges);
let mut g = Graph::new();
for i in 0..n {
g.add_node(i).unwrap();
}
for _ in 0..m {
let src = rng.next_u64() % n;
let dst = rng.next_u64() % n;
g.link(src, dst, 1, NO_PROPS).unwrap();
}
let s = Snapshot::of(&g);
let want = reference(&s);
let got = wcc(&s);
assert_eq!(got.labels(), want, "trial {trial}");
}
}
#[test]
fn a_graph_with_a_giant_component_comes_out_right() {
let mut rng = Rng::new(0x9a1);
let n = if cfg!(miri) { 40u64 } else { 20_000 };
let mut g = Graph::new();
for i in 0..n {
g.add_node(i).unwrap();
}
let big = n * 9 / 10;
for i in 0..big {
g.link(i, (i + 1) % big, 1, NO_PROPS).unwrap();
}
for _ in 0..n / 4 {
let src = big + rng.next_u64() % (n - big);
let dst = big + rng.next_u64() % (n - big);
g.link(src, dst, 1, NO_PROPS).unwrap();
}
let s = Snapshot::of(&g);
let want = reference(&s);
let got = wcc(&s);
assert_eq!(got.labels(), want);
let (label, size) = got.largest().unwrap();
assert_eq!(label, 0);
assert!(size >= big as u32, "the ring is one component: {size}");
}
}