use std::collections::VecDeque;
use crate::algorithms::paths::dijkstra::DijkstraMode;
use crate::core::Graph;
use crate::core::error::{IgraphError, IgraphResult};
use crate::core::graph::VertexId;
pub fn topological_sorting(graph: &Graph, mode: DijkstraMode) -> IgraphResult<Vec<VertexId>> {
if !graph.is_directed() {
return Err(IgraphError::InvalidArgument(
"topological_sorting requires a directed graph".to_string(),
));
}
if matches!(mode, DijkstraMode::All) {
return Err(IgraphError::InvalidArgument(
"topological_sorting does not accept DijkstraMode::All".to_string(),
));
}
let walk_forward = matches!(mode, DijkstraMode::Out);
let n = graph.vcount();
let n_us = n as usize;
let mut degrees: Vec<u32> = vec![0; n_us];
for v in 0..n {
let incidents = if walk_forward {
graph.incident_in(v)?
} else {
graph.incident(v)?
};
let mut count: u32 = 0;
for eid in incidents {
let other = graph.edge_other(eid, v)?;
if other != v {
count = count.saturating_add(1);
}
}
degrees[v as usize] = count;
}
let mut sources: VecDeque<VertexId> = VecDeque::new();
for v in 0..n {
if degrees[v as usize] == 0 {
sources.push_back(v);
}
}
let mut order: Vec<VertexId> = Vec::with_capacity(n_us);
while let Some(v) = sources.pop_front() {
order.push(v);
let outgoing = if walk_forward {
graph.incident(v)?
} else {
graph.incident_in(v)?
};
for eid in outgoing {
let nei = graph.edge_other(eid, v)?;
if nei == v {
continue;
}
let nei_idx = nei as usize;
degrees[nei_idx] = degrees[nei_idx].saturating_sub(1);
if degrees[nei_idx] == 0 {
sources.push_back(nei);
}
}
}
if order.len() != n_us {
return Err(IgraphError::InvalidArgument(
"topological_sorting: graph contains a cycle (no valid ordering exists)".to_string(),
));
}
Ok(order)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Graph;
#[test]
fn empty_directed_graph_returns_empty_order() {
let g = Graph::new(0, true).unwrap();
let order = topological_sorting(&g, DijkstraMode::Out).unwrap();
assert!(order.is_empty());
}
#[test]
fn isolated_vertices_only_returns_all_in_some_order() {
let g = Graph::new(3, true).unwrap();
let mut order = topological_sorting(&g, DijkstraMode::Out).unwrap();
order.sort_unstable();
assert_eq!(order, vec![0, 1, 2]);
}
#[test]
fn linear_chain_out_mode_preserves_order() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
let order = topological_sorting(&g, DijkstraMode::Out).unwrap();
assert_eq!(order, vec![0, 1, 2, 3]);
}
#[test]
fn linear_chain_in_mode_reverses_order() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
let order = topological_sorting(&g, DijkstraMode::In).unwrap();
assert_eq!(order, vec![3, 2, 1, 0]);
}
#[test]
fn diamond_dag_respects_edge_order() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 3), (2, 3)])
.unwrap();
let order = topological_sorting(&g, DijkstraMode::Out).unwrap();
assert_eq!(order[0], 0);
assert_eq!(order[3], 3);
assert!(
order == vec![0, 1, 2, 3] || order == vec![0, 2, 1, 3],
"unexpected order: {order:?}"
);
}
#[test]
fn self_loop_does_not_block_sorting() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
let order = topological_sorting(&g, DijkstraMode::Out).unwrap();
assert_eq!(order, vec![0, 1]);
}
#[test]
fn three_cycle_errors() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
let err = topological_sorting(&g, DijkstraMode::Out).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn undirected_graph_errors() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let err = topological_sorting(&g, DijkstraMode::Out).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn all_mode_errors() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
let err = topological_sorting(&g, DijkstraMode::All).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn parallel_edges_dont_block_ordering() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
let order = topological_sorting(&g, DijkstraMode::Out).unwrap();
assert_eq!(order, vec![0, 1]);
}
#[test]
fn topological_order_respects_every_edge() {
let mut g = Graph::new(5, true).unwrap();
g.add_edges(vec![(0u32, 2u32), (1, 2), (2, 3), (2, 4)])
.unwrap();
let order = topological_sorting(&g, DijkstraMode::Out).unwrap();
let pos: Vec<usize> = {
let mut p = vec![0usize; 5];
for (i, &v) in order.iter().enumerate() {
p[v as usize] = i;
}
p
};
let m = u32::try_from(g.ecount()).expect("ecount fits in u32 in test");
for e in 0..m {
let (u, v) = g.edge(e).unwrap();
if u == v {
continue;
}
assert!(
pos[u as usize] < pos[v as usize],
"edge {u}→{v} violates order"
);
}
}
#[test]
fn cycle_with_tail_still_errors() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0), (3, 0)])
.unwrap();
let err = topological_sorting(&g, DijkstraMode::Out).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
}