rust_igraph/algorithms/constructors/
prufer.rs1use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
32
33pub fn from_prufer(prufer: &[u32]) -> IgraphResult<Graph> {
57 let len = prufer.len();
58 let n = u32::try_from(len)
59 .ok()
60 .and_then(|l| l.checked_add(2))
61 .ok_or_else(|| {
62 IgraphError::InvalidArgument("from_prufer: prufer.len() + 2 overflows u32".to_string())
63 })?;
64
65 let mut degree: Vec<u32> = vec![0; n as usize];
67 for &w in prufer {
68 if w >= n {
69 return Err(IgraphError::InvalidArgument(format!(
70 "from_prufer: invalid Prufer entry {w} (must be < {n})",
71 )));
72 }
73 degree[w as usize] += 1;
74 }
75
76 let edge_count = (n - 1) as usize;
77 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(edge_count);
78
79 let mut v: u32 = 0;
84 let mut k: usize = 0;
85 let mut last_i: u32 = 0;
86 let limit = len; for i in 0..n {
88 last_i = i;
89 let mut u = i;
90 while k < limit && u <= i && degree[u as usize] == 0 {
91 v = prufer[k];
92 edges.push((v, u));
93 k += 1;
94 degree[v as usize] -= 1;
95 u = v;
96 }
97 if k == limit {
98 break;
99 }
100 }
101
102 let mut last_u: u32 = 0;
104 for cand in (last_i + 1)..n {
105 if degree[cand as usize] == 0 && cand != v {
106 last_u = cand;
107 break;
108 }
109 }
110 edges.push((v, last_u));
111
112 let mut g = Graph::new(n, false)?;
113 g.add_edges(edges)?;
114 Ok(g)
115}
116
117pub fn to_prufer(graph: &Graph) -> IgraphResult<Vec<u32>> {
142 let n = graph.vcount();
143
144 if n < 2 {
145 return Err(IgraphError::InvalidArgument(
146 "to_prufer: tree must have at least 2 vertices".into(),
147 ));
148 }
149
150 let ecount = graph.ecount();
152 if ecount != (n as usize) - 1 {
153 return Err(IgraphError::InvalidArgument(
154 "to_prufer: graph is not a tree (wrong edge count)".into(),
155 ));
156 }
157
158 let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n as usize];
160 for eid in 0..ecount {
161 #[allow(clippy::cast_possible_truncation)]
162 let (from, to) = graph.edge(eid as u32)?;
163 if from == to {
164 return Err(IgraphError::InvalidArgument(
165 "to_prufer: graph contains a self-loop, not a tree".into(),
166 ));
167 }
168 adj[from as usize].push(to);
169 adj[to as usize].push(from);
170 }
171
172 #[allow(clippy::cast_possible_truncation)]
174 let mut degrees: Vec<u32> = adj.iter().map(|v| v.len() as u32).collect();
175
176 let mut bfs_visited = vec![false; n as usize];
178 let mut bfs_queue = std::collections::VecDeque::new();
179 bfs_queue.push_back(0u32);
180 bfs_visited[0] = true;
181 let mut visit_count: u32 = 1;
182 while let Some(v) = bfs_queue.pop_front() {
183 for &nbr in &adj[v as usize] {
184 if !bfs_visited[nbr as usize] {
185 bfs_visited[nbr as usize] = true;
186 visit_count += 1;
187 bfs_queue.push_back(nbr);
188 }
189 }
190 }
191 if visit_count != n {
192 return Err(IgraphError::InvalidArgument(
193 "to_prufer: graph is not connected, not a tree".into(),
194 ));
195 }
196
197 let mut prufer: Vec<u32> = Vec::with_capacity((n - 2) as usize);
199 let mut prufer_idx: usize = 0;
200 let target_len = (n - 2) as usize;
201
202 for u in 0..n {
203 let mut leaf = u;
204 let mut deg = degrees[leaf as usize];
205
206 while deg == 1 && leaf <= u && prufer_idx < target_len {
207 degrees[leaf as usize] = 0;
208
209 let mut neighbor = 0u32;
211 for &nbr in &adj[leaf as usize] {
212 if degrees[nbr as usize] > 0 {
213 neighbor = nbr;
214 break;
215 }
216 }
217
218 degrees[neighbor as usize] -= 1;
219 deg = degrees[neighbor as usize];
220
221 if deg > 0 {
222 prufer.push(neighbor);
223 prufer_idx += 1;
224 }
225 leaf = neighbor;
226 }
227 }
228
229 Ok(prufer)
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use std::collections::BTreeSet;
236
237 fn collect_edges_canonical(g: &Graph) -> BTreeSet<(VertexId, VertexId)> {
238 let m = u32::try_from(g.ecount()).expect("ecount fits u32 in tests");
239 (0..m)
240 .map(|eid| g.edge(eid).expect("edge in range"))
241 .map(|(a, b)| if a <= b { (a, b) } else { (b, a) })
242 .collect()
243 }
244
245 fn uf_find(parent: &mut [u32], mut node: u32) -> u32 {
246 while parent[node as usize] != node {
247 let grand = parent[parent[node as usize] as usize];
248 parent[node as usize] = grand;
249 node = grand;
250 }
251 node
252 }
253
254 fn is_tree(graph: &Graph) -> bool {
255 let vcount = graph.vcount();
257 let ecount = graph.ecount();
258 if vcount == 0 {
259 return ecount == 0;
260 }
261 if ecount != (vcount as usize) - 1 {
262 return false;
263 }
264 let mut parent: Vec<u32> = (0..vcount).collect();
265 let em = u32::try_from(ecount).expect("ecount fits u32 in tests");
266 for eid in 0..em {
267 let (src, dst) = graph.edge(eid).expect("edge in range");
268 let rs = uf_find(&mut parent, src);
269 let rd = uf_find(&mut parent, dst);
270 if rs == rd {
271 return false; }
273 parent[rs as usize] = rd;
274 }
275 let root = uf_find(&mut parent, 0);
276 (1..vcount).all(|v| uf_find(&mut parent, v) == root)
277 }
278
279 #[test]
280 fn empty_prufer_yields_p2() {
281 let g = from_prufer(&[]).expect("empty");
282 assert_eq!(g.vcount(), 2);
283 assert_eq!(g.ecount(), 1);
284 assert!(!g.is_directed());
285 let edges = collect_edges_canonical(&g);
286 let expected: BTreeSet<(u32, u32)> = [(0, 1)].into_iter().collect();
287 assert_eq!(edges, expected);
288 }
289
290 #[test]
291 fn upstream_fixture_2_3_2_3() {
292 let g = from_prufer(&[2, 3, 2, 3]).expect("prufer1");
295 assert_eq!(g.vcount(), 6);
296 assert_eq!(g.ecount(), 5);
297 let edges = collect_edges_canonical(&g);
298 let expected: BTreeSet<(u32, u32)> = [(0, 2), (1, 3), (2, 4), (2, 3), (3, 5)]
299 .into_iter()
300 .collect();
301 assert_eq!(edges, expected);
302 assert!(is_tree(&g));
303 }
304
305 #[test]
306 fn upstream_fixture_0_2_4_1_1_0() {
307 let g = from_prufer(&[0, 2, 4, 1, 1, 0]).expect("prufer2");
310 assert_eq!(g.vcount(), 8);
311 assert_eq!(g.ecount(), 7);
312 let edges = collect_edges_canonical(&g);
313 let expected: BTreeSet<(u32, u32)> =
314 [(0, 3), (2, 5), (2, 4), (1, 4), (1, 6), (0, 1), (0, 7)]
315 .into_iter()
316 .collect();
317 assert_eq!(edges, expected);
318 assert!(is_tree(&g));
319 }
320
321 #[test]
322 fn invalid_entry_out_of_range_errors() {
323 let err = from_prufer(&[0, 4]).unwrap_err();
325 assert!(matches!(err, IgraphError::InvalidArgument(_)));
326 }
327
328 #[test]
329 fn constant_sequence_yields_star() {
330 let g = from_prufer(&[0, 0, 0, 0]).expect("star");
334 assert_eq!(g.vcount(), 6);
335 assert_eq!(g.ecount(), 5);
336 assert!(is_tree(&g));
337 let deg0 = g.neighbors(0).expect("neighbors of 0").len();
339 assert_eq!(deg0, 5);
340 }
341
342 #[test]
343 fn ascending_sequence_yields_path() {
344 let g = from_prufer(&[1, 2, 3, 4]).expect("path");
346 assert_eq!(g.vcount(), 6);
347 assert_eq!(g.ecount(), 5);
348 assert!(is_tree(&g));
349 for v in 0..6u32 {
351 let d = g.neighbors(v).expect("neighbors").len();
352 let expected = if v == 0 || v == 5 { 1 } else { 2 };
353 assert_eq!(d, expected, "vertex {v} degree");
354 }
355 }
356
357 #[test]
358 fn single_entry_prufer() {
359 let g = from_prufer(&[0]).expect("single");
361 assert_eq!(g.vcount(), 3);
362 assert_eq!(g.ecount(), 2);
363 assert!(is_tree(&g));
364 let deg0 = g.neighbors(0).expect("neighbors").len();
365 assert_eq!(deg0, 2); }
367
368 #[test]
369 fn always_undirected() {
370 let g = from_prufer(&[2, 3, 2, 3]).expect("ok");
371 assert!(!g.is_directed());
372 }
373
374 #[test]
375 fn no_self_loops_or_multi_edges() {
376 let g = from_prufer(&[0, 2, 4, 1, 1, 0]).expect("ok");
377 let m = u32::try_from(g.ecount()).expect("m fits u32 in tests");
378 let mut seen: BTreeSet<(u32, u32)> = BTreeSet::new();
379 for e in 0..m {
380 let (a, b) = g.edge(e).expect("edge");
381 assert_ne!(a, b, "tree must not have self-loops");
382 let canon = if a <= b { (a, b) } else { (b, a) };
383 assert!(seen.insert(canon), "duplicate edge {canon:?}");
384 }
385 }
386
387 #[test]
390 fn to_prufer_roundtrip_2_3_2_3() {
391 let tree = from_prufer(&[2, 3, 2, 3]).expect("decode");
392 let seq = to_prufer(&tree).expect("encode");
393 assert_eq!(seq, vec![2, 3, 2, 3]);
394 }
395
396 #[test]
397 fn to_prufer_roundtrip_constant() {
398 let tree = from_prufer(&[3, 3, 3]).expect("decode");
400 let seq = to_prufer(&tree).expect("encode");
401 assert_eq!(seq, vec![3, 3, 3]);
402 }
403
404 #[test]
405 fn to_prufer_roundtrip_path() {
406 let tree = from_prufer(&[1, 2]).expect("decode");
408 let seq = to_prufer(&tree).expect("encode");
409 assert_eq!(seq, vec![1, 2]);
410 }
411
412 #[test]
413 fn to_prufer_p2() {
414 let mut g = Graph::with_vertices(2);
416 g.add_edge(0, 1).expect("add edge");
417 let seq = to_prufer(&g).expect("encode");
418 assert!(seq.is_empty());
419 }
420
421 #[test]
422 fn to_prufer_not_a_tree_cycle() {
423 let mut g = Graph::with_vertices(3);
424 g.add_edge(0, 1).expect("ok");
425 g.add_edge(1, 2).expect("ok");
426 g.add_edge(2, 0).expect("ok");
427 assert!(to_prufer(&g).is_err());
428 }
429
430 #[test]
431 fn to_prufer_not_a_tree_disconnected() {
432 let mut g = Graph::with_vertices(4);
433 g.add_edge(0, 1).expect("ok");
434 g.add_edge(2, 3).expect("ok");
435 assert!(to_prufer(&g).is_err());
437 }
438
439 #[test]
440 fn to_prufer_single_vertex() {
441 let g = Graph::with_vertices(1);
442 assert!(to_prufer(&g).is_err());
443 }
444
445 #[test]
446 fn to_prufer_roundtrip_large() {
447 let seq = vec![0, 2, 4, 1, 1, 0];
448 let tree = from_prufer(&seq).expect("decode");
449 let result = to_prufer(&tree).expect("encode");
450 assert_eq!(result, seq);
451 }
452}
453
454#[cfg(all(test, feature = "proptest-harness"))]
455mod proptest_tests {
456 use super::*;
457 use proptest::prelude::*;
458 use std::collections::BTreeSet;
459
460 fn arb_prufer() -> impl Strategy<Value = Vec<u32>> {
461 (2u32..30).prop_flat_map(|n| prop::collection::vec(0u32..n, (n - 2) as usize))
464 }
465
466 fn uf_find(parent: &mut [u32], mut node: u32) -> u32 {
467 while parent[node as usize] != node {
468 let grand = parent[parent[node as usize] as usize];
469 parent[node as usize] = grand;
470 node = grand;
471 }
472 node
473 }
474
475 proptest! {
476 #[test]
477 fn always_a_tree(prufer in arb_prufer()) {
478 let g = from_prufer(&prufer).unwrap();
479 let n = u32::try_from(prufer.len()).unwrap() + 2;
480 prop_assert_eq!(g.vcount(), n);
481 prop_assert_eq!(g.ecount(), (n - 1) as usize);
482 prop_assert!(!g.is_directed());
483 }
484
485 #[test]
486 fn no_self_loops(prufer in arb_prufer()) {
487 let g = from_prufer(&prufer).unwrap();
488 let m = u32::try_from(g.ecount()).unwrap();
489 for e in 0..m {
490 let (a, b) = g.edge(e).unwrap();
491 prop_assert_ne!(a, b);
492 }
493 }
494
495 #[test]
496 fn no_duplicate_edges(prufer in arb_prufer()) {
497 let g = from_prufer(&prufer).unwrap();
498 let m = u32::try_from(g.ecount()).unwrap();
499 let mut seen: BTreeSet<(u32, u32)> = BTreeSet::new();
500 for e in 0..m {
501 let (a, b) = g.edge(e).unwrap();
502 let canon = if a <= b { (a, b) } else { (b, a) };
503 prop_assert!(seen.insert(canon));
504 }
505 }
506
507 #[test]
508 fn connected_undirected_tree(prufer in arb_prufer()) {
509 let graph = from_prufer(&prufer).unwrap();
511 let vcount = graph.vcount();
512 let mut parent: Vec<u32> = (0..vcount).collect();
513 let ecount = u32::try_from(graph.ecount()).unwrap();
514 for eid in 0..ecount {
515 let (src, dst) = graph.edge(eid).unwrap();
516 let rs = uf_find(&mut parent, src);
517 let rd = uf_find(&mut parent, dst);
518 prop_assert_ne!(rs, rd, "cycle detected — not a tree");
519 parent[rs as usize] = rd;
520 }
521 let root = uf_find(&mut parent, 0);
522 for v in 1..vcount {
523 prop_assert_eq!(uf_find(&mut parent, v), root);
524 }
525 }
526
527 #[test]
528 fn invalid_entry_errors(
529 n in 3u32..20,
530 bad in 20u32..30,
531 ) {
532 let len = (n - 2) as usize;
535 let mut p = vec![0u32; len];
536 p[0] = bad; let err = from_prufer(&p).unwrap_err();
538 prop_assert!(matches!(err, IgraphError::InvalidArgument(_)));
539 }
540 }
541}