pub fn greedy_aggregate(strong: &[Vec<usize>], n: usize) -> Vec<usize> {
const UNASSIGNED: usize = usize::MAX;
let mut aggregate_ids = vec![UNASSIGNED; n];
let mut next_agg = 0usize;
for i in 0..n {
if aggregate_ids[i] != UNASSIGNED {
continue;
}
let all_unmarked = strong[i].iter().all(|&j| aggregate_ids[j] == UNASSIGNED);
if all_unmarked {
let agg_id = next_agg;
next_agg += 1;
aggregate_ids[i] = agg_id;
for &j in &strong[i] {
if aggregate_ids[j] == UNASSIGNED {
aggregate_ids[j] = agg_id;
}
}
}
}
let mut changed = true;
while changed {
changed = false;
for i in 0..n {
if aggregate_ids[i] == UNASSIGNED {
for &j in &strong[i] {
if aggregate_ids[j] != UNASSIGNED {
aggregate_ids[i] = aggregate_ids[j];
changed = true;
break;
}
}
}
}
}
for agg_id in aggregate_ids.iter_mut().take(n) {
if *agg_id == UNASSIGNED {
*agg_id = next_agg;
next_agg += 1;
}
}
aggregate_ids
}
#[cfg(test)]
mod tests {
use super::*;
fn chain_strong(n: usize) -> Vec<Vec<usize>> {
(0..n)
.map(|i| {
let mut nbrs = Vec::new();
if i > 0 {
nbrs.push(i - 1);
}
if i + 1 < n {
nbrs.push(i + 1);
}
nbrs
})
.collect()
}
#[test]
fn test_aggregation_covers_all_nodes() {
let n = 12;
let strong = chain_strong(n);
let agg_ids = greedy_aggregate(&strong, n);
assert_eq!(agg_ids.len(), n);
for (i, &id) in agg_ids.iter().enumerate() {
assert!(
id != usize::MAX,
"Node {i} was not assigned to any aggregate"
);
}
let n_agg = *agg_ids.iter().max().unwrap_or(&0) + 1;
let mut seen = vec![false; n_agg];
for &id in &agg_ids {
seen[id] = true;
}
for (id, &s) in seen.iter().enumerate() {
assert!(s, "Aggregate {id} has no members");
}
}
#[test]
fn test_aggregation_connected() {
let n = 20;
let strong = chain_strong(n);
let agg_ids = greedy_aggregate(&strong, n);
let n_agg = *agg_ids.iter().max().unwrap_or(&0) + 1;
let mut sizes = vec![0usize; n_agg];
for &id in &agg_ids {
sizes[id] += 1;
}
let n_multi = sizes.iter().filter(|&&s| s > 1).count();
assert!(
n_multi > 0,
"Expected multi-node aggregates, but got sizes: {sizes:?}"
);
let total: usize = sizes.iter().sum();
assert_eq!(total, n, "Aggregates don't cover all nodes: {total} != {n}");
}
#[test]
fn test_aggregation_small_graph() {
let strong = vec![
vec![1usize, 2, 3],
vec![0usize, 2, 3],
vec![0usize, 1, 3],
vec![0usize, 1, 2],
];
let n = 4;
let agg_ids = greedy_aggregate(&strong, n);
let first_id = agg_ids[0];
assert!(
agg_ids.iter().all(|&id| id == first_id),
"Expected all nodes in same aggregate, got: {agg_ids:?}"
);
}
}