use super::Adjacency;
#[must_use]
pub fn core_numbers(g: &Adjacency) -> Vec<usize> {
let n = g.n;
let mut deg: Vec<usize> = (0..n).map(|i| g.degree(i)).collect();
let mut core = vec![0usize; n];
let mut removed = vec![false; n];
for _ in 0..n {
let mut pick = usize::MAX;
let mut best = usize::MAX;
for v in 0..n {
if !removed[v] && deg[v] < best {
best = deg[v];
pick = v;
}
}
if pick == usize::MAX {
break;
}
core[pick] = best;
removed[pick] = true;
for &(w, _) in &g.und[pick] {
if !removed[w] && deg[w] > best {
deg[w] -= 1;
}
}
}
core
}
#[must_use]
pub fn degeneracy(g: &Adjacency) -> usize {
core_numbers(g).into_iter().max().unwrap_or(0)
}
#[must_use]
pub fn k_core(g: &Adjacency, k: usize) -> Vec<usize> {
core_numbers(g).into_iter().enumerate().filter(|&(_, c)| c >= k).map(|(i, _)| i).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_triangle_is_a_2_core() {
let g = Adjacency::from_edges(3, &[(0, 1), (1, 2), (2, 0)]);
assert_eq!(core_numbers(&g), vec![2, 2, 2]);
assert_eq!(degeneracy(&g), 2);
assert_eq!(k_core(&g, 2).len(), 3);
assert!(k_core(&g, 3).is_empty());
}
#[test]
fn a_pendant_node_drops_out_of_the_core() {
let g = Adjacency::from_edges(4, &[(0, 1), (1, 2), (2, 0), (0, 3)]);
let core = core_numbers(&g);
assert_eq!(core[3], 1, "the pendant is a 1-core node");
assert_eq!(core[1], 2, "the triangle interior stays a 2-core");
assert_eq!(k_core(&g, 2), vec![0, 1, 2], "the 2-core is exactly the triangle");
}
#[test]
fn a_path_is_a_1_core() {
let g = Adjacency::from_edges(4, &[(0, 1), (1, 2), (2, 3)]);
assert_eq!(degeneracy(&g), 1);
}
#[test]
fn edgeless_graph_has_zero_cores() {
let g = Adjacency::from_edges(3, &[]);
assert_eq!(core_numbers(&g), vec![0, 0, 0]);
assert_eq!(degeneracy(&g), 0);
}
}