use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
pub fn from_prufer(prufer: &[u32]) -> IgraphResult<Graph> {
let len = prufer.len();
let n = u32::try_from(len)
.ok()
.and_then(|l| l.checked_add(2))
.ok_or_else(|| {
IgraphError::InvalidArgument("from_prufer: prufer.len() + 2 overflows u32".to_string())
})?;
let mut degree: Vec<u32> = vec![0; n as usize];
for &w in prufer {
if w >= n {
return Err(IgraphError::InvalidArgument(format!(
"from_prufer: invalid Prufer entry {w} (must be < {n})",
)));
}
degree[w as usize] += 1;
}
let edge_count = (n - 1) as usize;
let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(edge_count);
let mut v: u32 = 0;
let mut k: usize = 0;
let mut last_i: u32 = 0;
let limit = len; for i in 0..n {
last_i = i;
let mut u = i;
while k < limit && u <= i && degree[u as usize] == 0 {
v = prufer[k];
edges.push((v, u));
k += 1;
degree[v as usize] -= 1;
u = v;
}
if k == limit {
break;
}
}
let mut last_u: u32 = 0;
for cand in (last_i + 1)..n {
if degree[cand as usize] == 0 && cand != v {
last_u = cand;
break;
}
}
edges.push((v, last_u));
let mut g = Graph::new(n, false)?;
g.add_edges(edges)?;
Ok(g)
}
pub fn to_prufer(graph: &Graph) -> IgraphResult<Vec<u32>> {
let n = graph.vcount();
if n < 2 {
return Err(IgraphError::InvalidArgument(
"to_prufer: tree must have at least 2 vertices".into(),
));
}
let ecount = graph.ecount();
if ecount != (n as usize) - 1 {
return Err(IgraphError::InvalidArgument(
"to_prufer: graph is not a tree (wrong edge count)".into(),
));
}
let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n as usize];
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (from, to) = graph.edge(eid as u32)?;
if from == to {
return Err(IgraphError::InvalidArgument(
"to_prufer: graph contains a self-loop, not a tree".into(),
));
}
adj[from as usize].push(to);
adj[to as usize].push(from);
}
#[allow(clippy::cast_possible_truncation)]
let mut degrees: Vec<u32> = adj.iter().map(|v| v.len() as u32).collect();
let mut bfs_visited = vec![false; n as usize];
let mut bfs_queue = std::collections::VecDeque::new();
bfs_queue.push_back(0u32);
bfs_visited[0] = true;
let mut visit_count: u32 = 1;
while let Some(v) = bfs_queue.pop_front() {
for &nbr in &adj[v as usize] {
if !bfs_visited[nbr as usize] {
bfs_visited[nbr as usize] = true;
visit_count += 1;
bfs_queue.push_back(nbr);
}
}
}
if visit_count != n {
return Err(IgraphError::InvalidArgument(
"to_prufer: graph is not connected, not a tree".into(),
));
}
let mut prufer: Vec<u32> = Vec::with_capacity((n - 2) as usize);
let mut prufer_idx: usize = 0;
let target_len = (n - 2) as usize;
for u in 0..n {
let mut leaf = u;
let mut deg = degrees[leaf as usize];
while deg == 1 && leaf <= u && prufer_idx < target_len {
degrees[leaf as usize] = 0;
let mut neighbor = 0u32;
for &nbr in &adj[leaf as usize] {
if degrees[nbr as usize] > 0 {
neighbor = nbr;
break;
}
}
degrees[neighbor as usize] -= 1;
deg = degrees[neighbor as usize];
if deg > 0 {
prufer.push(neighbor);
prufer_idx += 1;
}
leaf = neighbor;
}
}
Ok(prufer)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
fn collect_edges_canonical(g: &Graph) -> BTreeSet<(VertexId, VertexId)> {
let m = u32::try_from(g.ecount()).expect("ecount fits u32 in tests");
(0..m)
.map(|eid| g.edge(eid).expect("edge in range"))
.map(|(a, b)| if a <= b { (a, b) } else { (b, a) })
.collect()
}
fn uf_find(parent: &mut [u32], mut node: u32) -> u32 {
while parent[node as usize] != node {
let grand = parent[parent[node as usize] as usize];
parent[node as usize] = grand;
node = grand;
}
node
}
fn is_tree(graph: &Graph) -> bool {
let vcount = graph.vcount();
let ecount = graph.ecount();
if vcount == 0 {
return ecount == 0;
}
if ecount != (vcount as usize) - 1 {
return false;
}
let mut parent: Vec<u32> = (0..vcount).collect();
let em = u32::try_from(ecount).expect("ecount fits u32 in tests");
for eid in 0..em {
let (src, dst) = graph.edge(eid).expect("edge in range");
let rs = uf_find(&mut parent, src);
let rd = uf_find(&mut parent, dst);
if rs == rd {
return false; }
parent[rs as usize] = rd;
}
let root = uf_find(&mut parent, 0);
(1..vcount).all(|v| uf_find(&mut parent, v) == root)
}
#[test]
fn empty_prufer_yields_p2() {
let g = from_prufer(&[]).expect("empty");
assert_eq!(g.vcount(), 2);
assert_eq!(g.ecount(), 1);
assert!(!g.is_directed());
let edges = collect_edges_canonical(&g);
let expected: BTreeSet<(u32, u32)> = [(0, 1)].into_iter().collect();
assert_eq!(edges, expected);
}
#[test]
fn upstream_fixture_2_3_2_3() {
let g = from_prufer(&[2, 3, 2, 3]).expect("prufer1");
assert_eq!(g.vcount(), 6);
assert_eq!(g.ecount(), 5);
let edges = collect_edges_canonical(&g);
let expected: BTreeSet<(u32, u32)> = [(0, 2), (1, 3), (2, 4), (2, 3), (3, 5)]
.into_iter()
.collect();
assert_eq!(edges, expected);
assert!(is_tree(&g));
}
#[test]
fn upstream_fixture_0_2_4_1_1_0() {
let g = from_prufer(&[0, 2, 4, 1, 1, 0]).expect("prufer2");
assert_eq!(g.vcount(), 8);
assert_eq!(g.ecount(), 7);
let edges = collect_edges_canonical(&g);
let expected: BTreeSet<(u32, u32)> =
[(0, 3), (2, 5), (2, 4), (1, 4), (1, 6), (0, 1), (0, 7)]
.into_iter()
.collect();
assert_eq!(edges, expected);
assert!(is_tree(&g));
}
#[test]
fn invalid_entry_out_of_range_errors() {
let err = from_prufer(&[0, 4]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn constant_sequence_yields_star() {
let g = from_prufer(&[0, 0, 0, 0]).expect("star");
assert_eq!(g.vcount(), 6);
assert_eq!(g.ecount(), 5);
assert!(is_tree(&g));
let deg0 = g.neighbors(0).expect("neighbors of 0").len();
assert_eq!(deg0, 5);
}
#[test]
fn ascending_sequence_yields_path() {
let g = from_prufer(&[1, 2, 3, 4]).expect("path");
assert_eq!(g.vcount(), 6);
assert_eq!(g.ecount(), 5);
assert!(is_tree(&g));
for v in 0..6u32 {
let d = g.neighbors(v).expect("neighbors").len();
let expected = if v == 0 || v == 5 { 1 } else { 2 };
assert_eq!(d, expected, "vertex {v} degree");
}
}
#[test]
fn single_entry_prufer() {
let g = from_prufer(&[0]).expect("single");
assert_eq!(g.vcount(), 3);
assert_eq!(g.ecount(), 2);
assert!(is_tree(&g));
let deg0 = g.neighbors(0).expect("neighbors").len();
assert_eq!(deg0, 2); }
#[test]
fn always_undirected() {
let g = from_prufer(&[2, 3, 2, 3]).expect("ok");
assert!(!g.is_directed());
}
#[test]
fn no_self_loops_or_multi_edges() {
let g = from_prufer(&[0, 2, 4, 1, 1, 0]).expect("ok");
let m = u32::try_from(g.ecount()).expect("m fits u32 in tests");
let mut seen: BTreeSet<(u32, u32)> = BTreeSet::new();
for e in 0..m {
let (a, b) = g.edge(e).expect("edge");
assert_ne!(a, b, "tree must not have self-loops");
let canon = if a <= b { (a, b) } else { (b, a) };
assert!(seen.insert(canon), "duplicate edge {canon:?}");
}
}
#[test]
fn to_prufer_roundtrip_2_3_2_3() {
let tree = from_prufer(&[2, 3, 2, 3]).expect("decode");
let seq = to_prufer(&tree).expect("encode");
assert_eq!(seq, vec![2, 3, 2, 3]);
}
#[test]
fn to_prufer_roundtrip_constant() {
let tree = from_prufer(&[3, 3, 3]).expect("decode");
let seq = to_prufer(&tree).expect("encode");
assert_eq!(seq, vec![3, 3, 3]);
}
#[test]
fn to_prufer_roundtrip_path() {
let tree = from_prufer(&[1, 2]).expect("decode");
let seq = to_prufer(&tree).expect("encode");
assert_eq!(seq, vec![1, 2]);
}
#[test]
fn to_prufer_p2() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).expect("add edge");
let seq = to_prufer(&g).expect("encode");
assert!(seq.is_empty());
}
#[test]
fn to_prufer_not_a_tree_cycle() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).expect("ok");
g.add_edge(1, 2).expect("ok");
g.add_edge(2, 0).expect("ok");
assert!(to_prufer(&g).is_err());
}
#[test]
fn to_prufer_not_a_tree_disconnected() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).expect("ok");
g.add_edge(2, 3).expect("ok");
assert!(to_prufer(&g).is_err());
}
#[test]
fn to_prufer_single_vertex() {
let g = Graph::with_vertices(1);
assert!(to_prufer(&g).is_err());
}
#[test]
fn to_prufer_roundtrip_large() {
let seq = vec![0, 2, 4, 1, 1, 0];
let tree = from_prufer(&seq).expect("decode");
let result = to_prufer(&tree).expect("encode");
assert_eq!(result, seq);
}
}
#[cfg(all(test, feature = "proptest-harness"))]
mod proptest_tests {
use super::*;
use proptest::prelude::*;
use std::collections::BTreeSet;
fn arb_prufer() -> impl Strategy<Value = Vec<u32>> {
(2u32..30).prop_flat_map(|n| prop::collection::vec(0u32..n, (n - 2) as usize))
}
fn uf_find(parent: &mut [u32], mut node: u32) -> u32 {
while parent[node as usize] != node {
let grand = parent[parent[node as usize] as usize];
parent[node as usize] = grand;
node = grand;
}
node
}
proptest! {
#[test]
fn always_a_tree(prufer in arb_prufer()) {
let g = from_prufer(&prufer).unwrap();
let n = u32::try_from(prufer.len()).unwrap() + 2;
prop_assert_eq!(g.vcount(), n);
prop_assert_eq!(g.ecount(), (n - 1) as usize);
prop_assert!(!g.is_directed());
}
#[test]
fn no_self_loops(prufer in arb_prufer()) {
let g = from_prufer(&prufer).unwrap();
let m = u32::try_from(g.ecount()).unwrap();
for e in 0..m {
let (a, b) = g.edge(e).unwrap();
prop_assert_ne!(a, b);
}
}
#[test]
fn no_duplicate_edges(prufer in arb_prufer()) {
let g = from_prufer(&prufer).unwrap();
let m = u32::try_from(g.ecount()).unwrap();
let mut seen: BTreeSet<(u32, u32)> = BTreeSet::new();
for e in 0..m {
let (a, b) = g.edge(e).unwrap();
let canon = if a <= b { (a, b) } else { (b, a) };
prop_assert!(seen.insert(canon));
}
}
#[test]
fn connected_undirected_tree(prufer in arb_prufer()) {
let graph = from_prufer(&prufer).unwrap();
let vcount = graph.vcount();
let mut parent: Vec<u32> = (0..vcount).collect();
let ecount = u32::try_from(graph.ecount()).unwrap();
for eid in 0..ecount {
let (src, dst) = graph.edge(eid).unwrap();
let rs = uf_find(&mut parent, src);
let rd = uf_find(&mut parent, dst);
prop_assert_ne!(rs, rd, "cycle detected — not a tree");
parent[rs as usize] = rd;
}
let root = uf_find(&mut parent, 0);
for v in 1..vcount {
prop_assert_eq!(uf_find(&mut parent, v), root);
}
}
#[test]
fn invalid_entry_errors(
n in 3u32..20,
bad in 20u32..30,
) {
let len = (n - 2) as usize;
let mut p = vec![0u32; len];
p[0] = bad; let err = from_prufer(&p).unwrap_err();
prop_assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
}
}