use crate::core::{Graph, IgraphResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SimplePathMode {
Out,
In,
All,
}
pub fn all_simple_paths(
graph: &Graph,
from: u32,
to: Option<&[u32]>,
mode: SimplePathMode,
min_len: i32,
max_len: i32,
max_results: i64,
) -> IgraphResult<Vec<Vec<u32>>> {
let vcount = graph.vcount();
if from >= vcount {
return Err(crate::core::IgraphError::InvalidArgument(
"source vertex out of range".to_string(),
));
}
let mut result: Vec<Vec<u32>> = Vec::new();
if max_results == 0 {
return Ok(result);
}
let target_set: Option<Vec<bool>> = to.map(|targets| {
let mut set = vec![false; vcount as usize];
for &t in targets {
if (t as usize) < set.len() {
set[t as usize] = true;
}
}
set
});
let adj = build_adjlist(graph, mode)?;
let mut stack: Vec<u32> = vec![from];
let mut dist: Vec<i32> = vec![0];
let mut added = vec![false; vcount as usize];
let mut nptr = vec![0usize; vcount as usize];
added[from as usize] = true;
while let Some(&act) = stack.last() {
let cur_dist = *dist.last().unwrap_or(&0);
let neis = &adj[act as usize];
let n = neis.len();
let ptr = &mut nptr[act as usize];
let within_dist = max_len < 0 || cur_dist < max_len;
let mut found_nei = false;
let mut nei = 0u32;
if within_dist {
while !found_nei && *ptr < n {
nei = neis[*ptr];
found_nei = !added[nei as usize];
*ptr += 1;
}
}
if within_dist && found_nei {
stack.push(nei);
dist.push(cur_dist + 1);
added[nei as usize] = true;
let is_target = match &target_set {
None => true,
Some(set) => set[nei as usize],
};
if is_target && (min_len <= 0 || cur_dist + 1 >= min_len) {
result.push(stack.clone());
if max_results >= 0
&& i64::try_from(result.len()).unwrap_or(i64::MAX) >= max_results
{
break;
}
}
} else {
let up = stack.pop().unwrap_or(0);
dist.pop();
added[up as usize] = false;
nptr[up as usize] = 0;
}
}
Ok(result)
}
fn build_adjlist(graph: &Graph, mode: SimplePathMode) -> IgraphResult<Vec<Vec<u32>>> {
let n = graph.vcount();
let mut adj = Vec::with_capacity(n as usize);
for v in 0..n {
let raw = match mode {
SimplePathMode::Out => {
if graph.is_directed() {
graph.out_neighbors_vec(v)?
} else {
graph.neighbors(v)?
}
}
SimplePathMode::In => {
if graph.is_directed() {
graph.in_neighbors_vec(v)?
} else {
graph.neighbors(v)?
}
}
SimplePathMode::All => {
if graph.is_directed() {
let mut all = graph.out_neighbors_vec(v)?;
all.extend(graph.in_neighbors_vec(v)?);
all
} else {
graph.neighbors(v)?
}
}
};
let mut deduped: Vec<u32> = Vec::with_capacity(raw.len());
for &nei in &raw {
if nei != v && !deduped.contains(&nei) {
deduped.push(nei);
}
}
adj.push(deduped);
}
Ok(adj)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::create;
#[test]
fn empty_graph() {
let g = Graph::with_vertices(0);
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, -1);
assert!(r.is_err());
}
#[test]
fn single_vertex() {
let g = Graph::with_vertices(1);
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, -1).expect("ok");
assert!(r.is_empty());
}
#[test]
fn single_edge() {
let g = create(&[(0, 1)], 2, false).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, -1).expect("ok");
assert_eq!(r.len(), 1);
assert_eq!(r[0], vec![0, 1]);
}
#[test]
fn path_3() {
let g = create(&[(0, 1), (1, 2)], 3, false).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, -1).expect("ok");
assert_eq!(r.len(), 2);
assert_eq!(r[0], vec![0, 1]);
assert_eq!(r[1], vec![0, 1, 2]);
}
#[test]
fn triangle_from_0() {
let g = create(&[(0, 1), (1, 2), (0, 2)], 3, false).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, -1).expect("ok");
assert_eq!(r.len(), 4);
}
#[test]
fn target_filter() {
let g = create(&[(0, 1), (1, 2), (2, 3)], 4, false).expect("ok");
let r = all_simple_paths(&g, 0, Some(&[3]), SimplePathMode::Out, 0, -1, -1).expect("ok");
assert_eq!(r.len(), 1);
assert_eq!(r[0], vec![0, 1, 2, 3]);
}
#[test]
fn max_len_filter() {
let g = create(&[(0, 1), (1, 2), (2, 3)], 4, false).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, 2, -1).expect("ok");
assert_eq!(r.len(), 2);
}
#[test]
fn min_len_filter() {
let g = create(&[(0, 1), (1, 2), (2, 3)], 4, false).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 2, -1, -1).expect("ok");
assert_eq!(r.len(), 2);
}
#[test]
fn max_results() {
let g = create(&[(0, 1), (1, 2), (2, 3)], 4, false).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, 2).expect("ok");
assert_eq!(r.len(), 2);
}
#[test]
fn directed_out() {
let g = create(&[(0, 1), (1, 2), (2, 0)], 3, true).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, -1).expect("ok");
assert_eq!(r.len(), 2);
assert_eq!(r[0], vec![0, 1]);
assert_eq!(r[1], vec![0, 1, 2]);
}
#[test]
fn directed_in() {
let g = create(&[(0, 1), (1, 2), (2, 0)], 3, true).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::In, 0, -1, -1).expect("ok");
assert_eq!(r.len(), 2);
assert_eq!(r[0], vec![0, 2]);
assert_eq!(r[1], vec![0, 2, 1]);
}
#[test]
fn directed_all() {
let g = create(&[(0, 1), (1, 2)], 3, true).expect("ok");
let r = all_simple_paths(&g, 1, None, SimplePathMode::All, 0, -1, -1).expect("ok");
assert_eq!(r.len(), 2);
}
#[test]
fn self_loop_ignored() {
let g = create(&[(0, 0), (0, 1)], 2, false).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, -1).expect("ok");
assert_eq!(r.len(), 1);
assert_eq!(r[0], vec![0, 1]);
}
#[test]
fn multi_edge_ignored() {
let g = create(&[(0, 1), (0, 1), (1, 2)], 3, false).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, -1).expect("ok");
assert_eq!(r.len(), 2);
assert_eq!(r[0], vec![0, 1]);
assert_eq!(r[1], vec![0, 1, 2]);
}
#[test]
fn disconnected() {
let g = create(&[(0, 1), (2, 3)], 4, false).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, -1).expect("ok");
assert_eq!(r.len(), 1);
assert_eq!(r[0], vec![0, 1]);
}
#[test]
fn source_out_of_range() {
let g = Graph::with_vertices(3);
let r = all_simple_paths(&g, 5, None, SimplePathMode::Out, 0, -1, -1);
assert!(r.is_err());
}
#[test]
fn max_results_zero() {
let g = create(&[(0, 1)], 2, false).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, 0).expect("ok");
assert!(r.is_empty());
}
#[test]
fn k4_complete() {
let g = create(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], 4, false).expect("ok");
let r = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, -1).expect("ok");
assert!(r.len() >= 9, "got {} paths", r.len());
}
}
#[cfg(all(test, feature = "proptest-harness"))]
mod proptests {
use super::*;
use crate::create;
use proptest::prelude::*;
fn arb_graph(max_v: u32) -> impl Strategy<Value = Graph> {
(2..=max_v).prop_flat_map(|n| {
let max_e = (n as usize)
.saturating_mul(n.saturating_sub(1) as usize)
.min(20);
proptest::collection::vec((0..n, 0..n), 0..=max_e).prop_map(move |edges| {
let edge_tuples: Vec<(u32, u32)> = edges.into_iter().collect();
create(&edge_tuples, n, false).expect("arb graph")
})
})
}
proptest! {
#[test]
fn all_paths_are_simple(g in arb_graph(6)) {
let n = g.vcount();
if n > 0 {
let paths = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, 100)
.expect("ok");
for path in &paths {
let mut seen = vec![false; n as usize];
for &v in path {
prop_assert!(
!seen[v as usize],
"vertex {v} repeated in path {:?}",
path
);
seen[v as usize] = true;
}
}
}
}
#[test]
fn all_paths_start_with_source(g in arb_graph(6)) {
let n = g.vcount();
if n > 0 {
let paths = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, 100)
.expect("ok");
for path in &paths {
prop_assert_eq!(
path[0], 0,
"path {:?} does not start with source 0",
path
);
}
}
}
#[test]
fn path_edges_exist(g in arb_graph(6)) {
let n = g.vcount();
if n > 0 {
let paths = all_simple_paths(&g, 0, None, SimplePathMode::Out, 0, -1, 100)
.expect("ok");
for path in &paths {
for w in path.windows(2) {
let neis = g.neighbors(w[0]).expect("ok");
prop_assert!(
neis.contains(&w[1]),
"edge {}->{} not in graph but in path {:?}",
w[0], w[1], path
);
}
}
}
}
}
}