#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Island {
pub bodies: Vec<usize>,
pub contacts: Vec<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ContactEdge {
pub body_a: usize,
pub body_b: usize,
}
struct UnionFind {
parent: Vec<usize>,
rank: Vec<u8>,
}
impl UnionFind {
fn new(n: usize) -> Self {
Self {
parent: (0..n).collect(),
rank: vec![0; n],
}
}
fn find(&mut self, mut x: usize) -> usize {
while self.parent[x] != x {
self.parent[x] = self.parent[self.parent[x]];
x = self.parent[x];
}
x
}
fn union(&mut self, a: usize, b: usize) -> bool {
let ra = self.find(a);
let rb = self.find(b);
if ra == rb {
return false;
}
match self.rank[ra].cmp(&self.rank[rb]) {
std::cmp::Ordering::Less => self.parent[ra] = rb,
std::cmp::Ordering::Greater => self.parent[rb] = ra,
std::cmp::Ordering::Equal => {
self.parent[rb] = ra;
self.rank[ra] += 1;
}
}
true
}
}
#[must_use]
pub fn detect_islands(
num_bodies: usize,
contacts: &[ContactEdge],
sleeping: Option<&[bool]>,
) -> (Vec<Island>, Vec<Island>) {
if num_bodies == 0 {
return (Vec::new(), Vec::new());
}
let mut uf = UnionFind::new(num_bodies);
for edge in contacts {
if edge.body_a < num_bodies && edge.body_b < num_bodies {
uf.union(edge.body_a, edge.body_b);
}
}
let mut component_index: Vec<usize> = vec![usize::MAX; num_bodies];
let mut bodies_per_component: Vec<Vec<usize>> = Vec::new();
for body in 0..num_bodies {
let root = uf.find(body);
if component_index[root] == usize::MAX {
component_index[root] = bodies_per_component.len();
bodies_per_component.push(Vec::new());
}
let idx = component_index[root];
bodies_per_component[idx].push(body);
}
let num_components = bodies_per_component.len();
let mut contacts_per_component: Vec<Vec<usize>> = vec![Vec::new(); num_components];
for (ci, edge) in contacts.iter().enumerate() {
if edge.body_a < num_bodies && edge.body_b < num_bodies {
let root = uf.find(edge.body_a);
let idx = component_index[root];
contacts_per_component[idx].push(ci);
}
}
let is_body_sleeping = |body: usize| -> bool {
match sleeping {
Some(s) => *s.get(body).unwrap_or(&false),
None => false,
}
};
let mut active_islands: Vec<Island> = Vec::new();
let mut sleeping_islands: Vec<Island> = Vec::new();
for (idx, bodies) in bodies_per_component.into_iter().enumerate() {
let island = Island {
bodies,
contacts: contacts_per_component[idx].clone(),
};
if sleeping.is_some() && island.bodies.iter().all(|&b| is_body_sleeping(b)) {
sleeping_islands.push(island);
} else {
active_islands.push(island);
}
}
(active_islands, sleeping_islands)
}
#[cfg(test)]
mod tests {
use super::*;
fn sorted_body_sets(islands: &[Island]) -> Vec<Vec<usize>> {
let mut sets: Vec<Vec<usize>> = islands
.iter()
.map(|i| {
let mut b = i.bodies.clone();
b.sort_unstable();
b
})
.collect();
sets.sort();
sets
}
#[test]
fn no_bodies_returns_empty() {
let (active, sleeping) = detect_islands(0, &[], None);
assert!(active.is_empty());
assert!(sleeping.is_empty());
}
#[test]
fn singleton_islands_no_contacts() {
let (active, sleeping) = detect_islands(4, &[], None);
assert_eq!(active.len(), 4);
assert!(sleeping.is_empty());
let sets = sorted_body_sets(&active);
assert_eq!(sets, vec![vec![0], vec![1], vec![2], vec![3]]);
}
#[test]
fn single_contact_merges_two_bodies() {
let contacts = [ContactEdge {
body_a: 0,
body_b: 1,
}];
let (active, _) = detect_islands(3, &contacts, None);
let sets = sorted_body_sets(&active);
assert!(sets.contains(&vec![0, 1]));
assert!(sets.contains(&vec![2]));
assert_eq!(active.len(), 2);
}
#[test]
fn chain_merges_all_into_one_island() {
let contacts = [
ContactEdge {
body_a: 0,
body_b: 1,
},
ContactEdge {
body_a: 1,
body_b: 2,
},
ContactEdge {
body_a: 2,
body_b: 3,
},
];
let (active, _) = detect_islands(4, &contacts, None);
assert_eq!(active.len(), 1);
let mut bodies = active[0].bodies.clone();
bodies.sort_unstable();
assert_eq!(bodies, vec![0, 1, 2, 3]);
}
#[test]
fn contacts_attributed_to_correct_island() {
let contacts = [
ContactEdge {
body_a: 0,
body_b: 1,
},
ContactEdge {
body_a: 2,
body_b: 3,
},
];
let (active, _) = detect_islands(4, &contacts, None);
assert_eq!(active.len(), 2);
for island in &active {
let mut b = island.bodies.clone();
b.sort_unstable();
if b == vec![0, 1] {
assert_eq!(island.contacts, vec![0]);
} else if b == vec![2, 3] {
assert_eq!(island.contacts, vec![1]);
} else {
panic!("unexpected island bodies: {b:?}");
}
}
}
#[test]
fn out_of_range_contacts_ignored() {
let contacts = [ContactEdge {
body_a: 0,
body_b: 99,
}];
let (active, _) = detect_islands(3, &contacts, None);
assert_eq!(active.len(), 3);
}
#[test]
fn duplicate_contacts_do_not_split_island() {
let contacts = [
ContactEdge {
body_a: 0,
body_b: 1,
},
ContactEdge {
body_a: 0,
body_b: 1,
},
];
let (active, _) = detect_islands(2, &contacts, None);
assert_eq!(active.len(), 1);
let mut c = active[0].contacts.clone();
c.sort_unstable();
assert_eq!(c, vec![0, 1]);
}
#[test]
fn sleeping_bodies_form_sleeping_island() {
let contacts = [ContactEdge {
body_a: 0,
body_b: 1,
}];
let sleeping = [true, true, false];
let (active, sleeping_islands) = detect_islands(3, &contacts, Some(&sleeping));
assert_eq!(sleeping_islands.len(), 1);
let mut sb = sleeping_islands[0].bodies.clone();
sb.sort_unstable();
assert_eq!(sb, vec![0, 1]);
assert_eq!(active.len(), 1);
assert_eq!(active[0].bodies, vec![2]);
}
#[test]
fn mixed_island_is_active_not_sleeping() {
let contacts = [ContactEdge {
body_a: 0,
body_b: 1,
}];
let sleeping = [true, false];
let (active, sleeping_islands) = detect_islands(2, &contacts, Some(&sleeping));
assert_eq!(sleeping_islands.len(), 0);
assert_eq!(active.len(), 1);
}
#[test]
fn no_sleeping_slice_means_no_sleeping_islands() {
let contacts = [ContactEdge {
body_a: 0,
body_b: 1,
}];
let (active, sleeping_islands) = detect_islands(2, &contacts, None);
assert!(sleeping_islands.is_empty());
assert_eq!(active.len(), 1);
}
#[test]
fn sleeping_slice_shorter_than_num_bodies_treated_as_awake() {
let contacts = [ContactEdge {
body_a: 0,
body_b: 1,
}];
let sleeping = [true]; let (active, sleeping_islands) = detect_islands(2, &contacts, Some(&sleeping));
assert!(sleeping_islands.is_empty());
assert_eq!(active.len(), 1);
}
#[test]
fn large_disconnected_graph() {
let contacts = [
ContactEdge {
body_a: 0,
body_b: 1,
},
ContactEdge {
body_a: 2,
body_b: 3,
},
ContactEdge {
body_a: 4,
body_b: 5,
},
];
let (active, _) = detect_islands(10, &contacts, None);
assert_eq!(active.len(), 7);
}
#[test]
fn star_topology_single_island() {
let contacts = [
ContactEdge {
body_a: 0,
body_b: 1,
},
ContactEdge {
body_a: 0,
body_b: 2,
},
ContactEdge {
body_a: 0,
body_b: 3,
},
ContactEdge {
body_a: 0,
body_b: 4,
},
];
let (active, _) = detect_islands(5, &contacts, None);
assert_eq!(active.len(), 1);
assert_eq!(active[0].bodies.len(), 5);
}
}