#![allow(dead_code)]
use std::collections::{HashMap, HashSet, VecDeque};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GraphComplexity {
Trivial,
Simple,
Moderate,
Complex,
}
impl GraphComplexity {
pub fn description(&self) -> &'static str {
match self {
GraphComplexity::Trivial => "trivial (≤2 nodes)",
GraphComplexity::Simple => "simple (3–8 nodes, linear)",
GraphComplexity::Moderate => "moderate (9–24 nodes, branching)",
GraphComplexity::Complex => "complex (≥25 nodes or dense edges)",
}
}
pub fn requires_advanced_scheduling(&self) -> bool {
matches!(self, GraphComplexity::Moderate | GraphComplexity::Complex)
}
}
#[derive(Debug, Clone)]
pub struct GraphStats {
node_count: usize,
edge_count: usize,
max_depth: usize,
total_degree: usize,
}
impl GraphStats {
pub fn new(node_count: usize, edge_count: usize, max_depth: usize) -> Self {
Self {
node_count,
edge_count,
max_depth,
total_degree: edge_count,
}
}
pub fn node_count(&self) -> usize {
self.node_count
}
pub fn edge_count(&self) -> usize {
self.edge_count
}
#[allow(clippy::cast_precision_loss)]
pub fn avg_degree(&self) -> f64 {
if self.node_count == 0 {
return 0.0;
}
self.edge_count as f64 / self.node_count as f64
}
pub fn depth(&self) -> usize {
self.max_depth
}
pub fn complexity(&self) -> GraphComplexity {
if self.node_count <= 2 {
GraphComplexity::Trivial
} else if self.node_count <= 8 && self.avg_degree() <= 1.5 {
GraphComplexity::Simple
} else if self.node_count < 25 {
GraphComplexity::Moderate
} else {
GraphComplexity::Complex
}
}
pub fn is_disconnected(&self) -> bool {
self.edge_count == 0
}
}
#[derive(Debug, Clone, Default)]
pub struct GraphAnalyzer {
adjacency: HashMap<u64, Vec<u64>>,
}
impl GraphAnalyzer {
pub fn new() -> Self {
Self {
adjacency: HashMap::new(),
}
}
pub fn add_node(&mut self, id: u64) {
self.adjacency.entry(id).or_default();
}
pub fn add_edge(&mut self, from: u64, to: u64) {
self.adjacency.entry(from).or_default().push(to);
self.adjacency.entry(to).or_default();
}
fn max_depth(&self) -> usize {
let mut in_degree: HashMap<u64, usize> = self.adjacency.keys().map(|&k| (k, 0)).collect();
for successors in self.adjacency.values() {
for &succ in successors {
*in_degree.entry(succ).or_insert(0) += 1;
}
}
let mut depth_map: HashMap<u64, usize> = HashMap::new();
let mut queue: VecDeque<u64> = in_degree
.iter()
.filter_map(|(&n, &d)| if d == 0 { Some(n) } else { None })
.collect();
for &n in &queue {
depth_map.insert(n, 0);
}
let mut max_d = 0;
while let Some(node) = queue.pop_front() {
let cur_depth = *depth_map.get(&node).unwrap_or(&0);
if let Some(succs) = self.adjacency.get(&node) {
for &succ in succs {
let new_depth = cur_depth + 1;
let entry = depth_map.entry(succ).or_insert(0);
if new_depth > *entry {
*entry = new_depth;
if new_depth > max_d {
max_d = new_depth;
}
queue.push_back(succ);
}
}
}
}
max_d
}
pub fn nodes(&self) -> HashSet<u64> {
let mut nodes: HashSet<u64> = self.adjacency.keys().copied().collect();
for succs in self.adjacency.values() {
for &s in succs {
nodes.insert(s);
}
}
nodes
}
pub fn edge_count(&self) -> usize {
self.adjacency.values().map(|v| v.len()).sum()
}
pub fn analyze(&self) -> GraphStats {
let node_count = self.nodes().len();
let edge_count = self.edge_count();
let max_depth = self.max_depth();
GraphStats::new(node_count, edge_count, max_depth)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_complexity_description_trivial() {
assert!(GraphComplexity::Trivial.description().contains("trivial"));
}
#[test]
fn test_complexity_requires_advanced_trivial() {
assert!(!GraphComplexity::Trivial.requires_advanced_scheduling());
}
#[test]
fn test_complexity_requires_advanced_complex() {
assert!(GraphComplexity::Complex.requires_advanced_scheduling());
}
#[test]
fn test_graph_stats_avg_degree_zero_nodes() {
let s = GraphStats::new(0, 0, 0);
assert_eq!(s.avg_degree(), 0.0);
}
#[test]
fn test_graph_stats_avg_degree() {
let s = GraphStats::new(4, 4, 3);
assert!((s.avg_degree() - 1.0).abs() < 1e-9);
}
#[test]
fn test_graph_stats_complexity_trivial() {
let s = GraphStats::new(2, 1, 1);
assert_eq!(s.complexity(), GraphComplexity::Trivial);
}
#[test]
fn test_graph_stats_complexity_simple() {
let s = GraphStats::new(5, 4, 4);
assert_eq!(s.complexity(), GraphComplexity::Simple);
}
#[test]
fn test_graph_stats_complexity_moderate() {
let s = GraphStats::new(10, 12, 5);
assert_eq!(s.complexity(), GraphComplexity::Moderate);
}
#[test]
fn test_graph_stats_complexity_complex() {
let s = GraphStats::new(30, 60, 10);
assert_eq!(s.complexity(), GraphComplexity::Complex);
}
#[test]
fn test_graph_stats_is_disconnected() {
let s = GraphStats::new(3, 0, 0);
assert!(s.is_disconnected());
}
#[test]
fn test_analyzer_empty_graph() {
let a = GraphAnalyzer::new();
let stats = a.analyze();
assert_eq!(stats.node_count(), 0);
assert_eq!(stats.edge_count(), 0);
assert_eq!(stats.depth(), 0);
}
#[test]
fn test_analyzer_linear_chain() {
let mut a = GraphAnalyzer::new();
a.add_edge(0, 1);
a.add_edge(1, 2);
a.add_edge(2, 3);
let stats = a.analyze();
assert_eq!(stats.node_count(), 4);
assert_eq!(stats.edge_count(), 3);
assert_eq!(stats.depth(), 3);
}
#[test]
fn test_analyzer_branching_graph() {
let mut a = GraphAnalyzer::new();
a.add_edge(0, 1);
a.add_edge(0, 2);
a.add_edge(1, 3);
a.add_edge(2, 3);
let stats = a.analyze();
assert_eq!(stats.node_count(), 4);
assert_eq!(stats.edge_count(), 4);
assert_eq!(stats.depth(), 2);
}
#[test]
fn test_analyzer_isolated_nodes() {
let mut a = GraphAnalyzer::new();
a.add_node(0);
a.add_node(1);
let stats = a.analyze();
assert_eq!(stats.node_count(), 2);
assert_eq!(stats.edge_count(), 0);
assert_eq!(stats.depth(), 0);
}
#[test]
fn test_complexity_moderate_requires_advanced() {
assert!(GraphComplexity::Moderate.requires_advanced_scheduling());
}
}