use std::collections::HashMap;
use super::types::{DiagramEdge, DiagramNode};
#[derive(Clone, Debug)]
#[allow(dead_code)] pub(crate) struct IndexedGraph {
node_to_index: HashMap<String, usize>,
index_to_node: Vec<String>,
adjacency_out: Vec<Vec<usize>>,
adjacency_in: Vec<Vec<usize>>,
edge_pairs: Vec<(usize, usize)>,
}
#[allow(dead_code)] impl IndexedGraph {
pub(crate) fn build(nodes: &[DiagramNode], edges: &[DiagramEdge]) -> Self {
let mut node_to_index = HashMap::with_capacity(nodes.len());
let mut index_to_node = Vec::with_capacity(nodes.len());
for (i, node) in nodes.iter().enumerate() {
node_to_index.insert(node.id().to_string(), i);
index_to_node.push(node.id().to_string());
}
let n = nodes.len();
let mut adjacency_out = vec![Vec::new(); n];
let mut adjacency_in = vec![Vec::new(); n];
let mut edge_pairs = Vec::with_capacity(edges.len());
for edge in edges {
if let (Some(&from_idx), Some(&to_idx)) =
(node_to_index.get(edge.from()), node_to_index.get(edge.to()))
{
adjacency_out[from_idx].push(to_idx);
adjacency_in[to_idx].push(from_idx);
edge_pairs.push((from_idx, to_idx));
}
}
Self {
node_to_index,
index_to_node,
adjacency_out,
adjacency_in,
edge_pairs,
}
}
pub(crate) fn node_count(&self) -> usize {
self.index_to_node.len()
}
pub(crate) fn index_of(&self, id: &str) -> Option<usize> {
self.node_to_index.get(id).copied()
}
pub(crate) fn id_of(&self, index: usize) -> Option<&str> {
self.index_to_node.get(index).map(|s| s.as_str())
}
pub(crate) fn successors(&self, index: usize) -> &[usize] {
self.adjacency_out.get(index).map_or(&[], |v| v.as_slice())
}
pub(crate) fn predecessors(&self, index: usize) -> &[usize] {
self.adjacency_in.get(index).map_or(&[], |v| v.as_slice())
}
pub(crate) fn edge_pairs(&self) -> &[(usize, usize)] {
&self.edge_pairs
}
pub(crate) fn roots(&self) -> Vec<usize> {
(0..self.node_count())
.filter(|&i| self.adjacency_in[i].is_empty())
.collect()
}
pub(crate) fn has_edge(&self, from: usize, to: usize) -> bool {
self.adjacency_out
.get(from)
.is_some_and(|out| out.contains(&to))
}
pub(crate) fn neighbors(&self, index: usize) -> Vec<usize> {
let mut result = Vec::new();
if let Some(out) = self.adjacency_out.get(index) {
result.extend(out);
}
if let Some(inc) = self.adjacency_in.get(index) {
for &pred in inc {
if !result.contains(&pred) {
result.push(pred);
}
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_node(id: &str) -> DiagramNode {
DiagramNode::new(id, id)
}
fn make_edge(from: &str, to: &str) -> DiagramEdge {
DiagramEdge::new(from, to)
}
#[test]
fn test_empty_graph() {
let g = IndexedGraph::build(&[], &[]);
assert_eq!(g.node_count(), 0);
assert!(g.roots().is_empty());
assert!(g.edge_pairs().is_empty());
}
#[test]
fn test_single_node() {
let nodes = vec![make_node("a")];
let g = IndexedGraph::build(&nodes, &[]);
assert_eq!(g.node_count(), 1);
assert_eq!(g.index_of("a"), Some(0));
assert_eq!(g.id_of(0), Some("a"));
assert_eq!(g.roots(), vec![0]);
assert!(g.successors(0).is_empty());
assert!(g.predecessors(0).is_empty());
}
#[test]
fn test_linear_graph() {
let nodes = vec![make_node("a"), make_node("b"), make_node("c")];
let edges = vec![make_edge("a", "b"), make_edge("b", "c")];
let g = IndexedGraph::build(&nodes, &edges);
assert_eq!(g.node_count(), 3);
assert_eq!(g.roots(), vec![0]); assert_eq!(g.successors(0), &[1]);
assert_eq!(g.successors(1), &[2]);
assert!(g.successors(2).is_empty());
assert_eq!(g.predecessors(2), &[1]);
assert!(g.has_edge(0, 1));
assert!(!g.has_edge(1, 0));
}
#[test]
fn test_diamond_graph() {
let nodes = vec![
make_node("a"),
make_node("b"),
make_node("c"),
make_node("d"),
];
let edges = vec![
make_edge("a", "b"),
make_edge("a", "c"),
make_edge("b", "d"),
make_edge("c", "d"),
];
let g = IndexedGraph::build(&nodes, &edges);
assert_eq!(g.roots(), vec![0]);
assert_eq!(g.successors(0), &[1, 2]);
assert_eq!(g.predecessors(3), &[1, 2]); }
#[test]
fn test_unknown_edge_skipped() {
let nodes = vec![make_node("a")];
let edges = vec![make_edge("a", "nonexistent")];
let g = IndexedGraph::build(&nodes, &edges);
assert!(g.edge_pairs().is_empty());
assert!(g.successors(0).is_empty());
}
#[test]
fn test_cycle() {
let nodes = vec![make_node("a"), make_node("b")];
let edges = vec![make_edge("a", "b"), make_edge("b", "a")];
let g = IndexedGraph::build(&nodes, &edges);
assert!(g.roots().is_empty()); assert_eq!(g.successors(0), &[1]);
assert_eq!(g.successors(1), &[0]);
assert!(g.has_edge(0, 1));
assert!(g.has_edge(1, 0));
}
#[test]
fn test_neighbors() {
let nodes = vec![make_node("a"), make_node("b"), make_node("c")];
let edges = vec![make_edge("a", "b"), make_edge("c", "a")];
let g = IndexedGraph::build(&nodes, &edges);
let mut neighbors = g.neighbors(0);
neighbors.sort();
assert_eq!(neighbors, vec![1, 2]); }
#[test]
fn test_self_loop() {
let nodes = vec![make_node("a")];
let edges = vec![make_edge("a", "a")];
let g = IndexedGraph::build(&nodes, &edges);
assert_eq!(g.successors(0), &[0]);
assert_eq!(g.predecessors(0), &[0]);
assert!(g.has_edge(0, 0));
}
#[test]
fn test_multiple_roots() {
let nodes = vec![make_node("a"), make_node("b"), make_node("c")];
let edges = vec![make_edge("a", "c"), make_edge("b", "c")];
let g = IndexedGraph::build(&nodes, &edges);
let mut roots = g.roots();
roots.sort();
assert_eq!(roots, vec![0, 1]);
}
}