use core::hash::Hash;
use std::collections::{HashMap, HashSet};
#[derive(Debug)]
pub enum DependencyError {
SelfReference,
CircularDependency,
}
impl std::fmt::Display for DependencyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
DependencyError::SelfReference => write!(f, "Self reference"),
DependencyError::CircularDependency => write!(f, "Circular dependency"),
}
}
}
impl std::error::Error for DependencyError {}
type DirectDependencyMap<T> = HashMap<T, HashSet<T>>;
fn dependency_map_remove_node<T: Eq + Hash>(map: &mut DirectDependencyMap<T>, node: &T) {
map.remove(node);
for (_, deps) in &mut *map {
deps.remove(&node);
}
map.retain(|_, deps| deps.len() > 0);
}
#[derive(Clone)]
pub struct AcyclicDependencyGraph<T> {
nodes: HashSet<T>,
forward_dependencies: DirectDependencyMap<T>,
backward_dependencies: DirectDependencyMap<T>,
}
impl<T> AcyclicDependencyGraph<T>
where
T: Eq + Hash + Clone,
{
pub fn new() -> Self {
AcyclicDependencyGraph {
nodes: HashSet::new(),
forward_dependencies: HashMap::new(),
backward_dependencies: HashMap::new(),
}
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
fn remove_node(&mut self, node: T) {
self.nodes.remove(&node);
dependency_map_remove_node(&mut self.forward_dependencies, &node);
dependency_map_remove_node(&mut self.backward_dependencies, &node);
}
pub fn get_leaves(&self) -> HashSet<T> {
let mut leaves: HashSet<T> = HashSet::new();
for node in &self.nodes {
if self.forward_dependencies.get(&node).is_none() {
leaves.insert(node.clone());
}
}
return leaves;
}
pub fn get_roots(&self) -> HashSet<T> {
let mut roots: HashSet<T> = HashSet::new();
for node in &self.nodes {
if self.backward_dependencies.get(&node).is_none() {
roots.insert(node.clone());
}
}
return roots;
}
pub fn depend_on(&mut self, from: T, to: T) -> Result<(), DependencyError> {
if from == to {
return Err(DependencyError::SelfReference);
}
if self.depends_on(&to, &from) {
return Err(DependencyError::CircularDependency);
}
self.nodes.insert(from.clone());
self.nodes.insert(to.clone());
self.forward_dependencies
.entry(from.clone())
.or_insert(HashSet::new())
.insert(to.clone());
self.backward_dependencies
.entry(to)
.or_insert(HashSet::new())
.insert(from);
return Ok(());
}
pub fn depends_on(&self, source: &T, target: &T) -> bool {
self.get_forward_dependencies(source).contains(target)
}
pub fn get_forward_dependencies(&self, node: &T) -> HashSet<T> {
let mut out = HashSet::new();
let mut discovered = vec![node];
while discovered.len() > 0 {
let mut discoveries = Vec::new();
for node in discovered {
let direct_dependencies: &HashSet<T> = match self.forward_dependencies.get(&node) {
Some(deps) => deps,
None => continue,
};
for node in direct_dependencies {
match out.insert(node.clone()) {
true => discoveries.push(node),
false => continue,
}
}
}
discovered = discoveries;
}
return out;
}
pub fn get_backward_dependencies(&self, node: &T) -> HashSet<T> {
let mut out = HashSet::new();
let mut discovered = vec![node];
while discovered.len() > 0 {
let mut discoveries = Vec::new();
for node in discovered {
let direct_dependencies: &HashSet<T> = match self.backward_dependencies.get(&node) {
Some(deps) => deps,
None => continue,
};
for node in direct_dependencies {
match out.insert(node.clone()) {
true => discoveries.push(node),
false => continue,
}
}
}
discovered = discoveries;
}
return out;
}
pub fn get_forward_dependency_topological_layers(&self) -> Vec<HashSet<T>> {
let mut layers = Vec::new();
let mut shrinking_graph = self.clone();
loop {
let leaves = shrinking_graph.get_leaves();
if leaves.len() == 0 {
break;
}
for leaf in &leaves {
shrinking_graph.remove_node(leaf.clone());
}
layers.push(leaves);
}
return layers;
}
pub fn get_backward_dependency_topological_layers(&self) -> Vec<HashSet<T>> {
let mut layers = Vec::new();
let mut shrinking_graph = self.clone();
loop {
let roots = shrinking_graph.get_roots();
if roots.len() == 0 {
break;
}
for root in &roots {
shrinking_graph.remove_node(root.clone());
}
layers.push(roots);
}
return layers;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph_is_empty() {
let graph: AcyclicDependencyGraph<&str> = AcyclicDependencyGraph::new();
assert!(graph.is_empty());
}
#[test]
fn non_empty_graph_is_not_empty() {
let mut graph = AcyclicDependencyGraph::new();
graph.depend_on("a", "b").unwrap();
assert!(!graph.is_empty());
}
#[test]
fn self_referential_dependencies_detected() {
let mut graph = AcyclicDependencyGraph::new();
assert!(graph.depend_on("a", "a").is_err());
}
#[test]
fn circular_dependencies_detected() {
let mut graph = AcyclicDependencyGraph::new();
graph.depend_on("a", "b").unwrap();
graph.depend_on("b", "c").unwrap();
assert!(graph.depend_on("c", "a").is_err());
}
#[test]
fn simple_topological_sort_forward() {
let mut graph = AcyclicDependencyGraph::new();
graph.depend_on("cake", "eggs").unwrap();
graph.depend_on("cake", "flour").unwrap();
graph.depend_on("eggs", "chickens").unwrap();
graph.depend_on("flour", "grain").unwrap();
graph.depend_on("chickens", "grain").unwrap();
graph.depend_on("grain", "soil").unwrap();
graph.depend_on("grain", "water").unwrap();
graph.depend_on("chickens", "water").unwrap();
let layers = graph.get_forward_dependency_topological_layers();
println!("layers: {:?}", layers);
let fwd_bckwd_check = |node: &str, expected_fwd: Vec<&str>, expected_bwd: Vec<&str>| {
let fwd = graph.get_forward_dependencies(&node);
let bwd = graph.get_backward_dependencies(&node);
assert_eq!(fwd.len(), expected_fwd.len());
assert_eq!(bwd.len(), expected_bwd.len());
for expected_node in expected_fwd {
assert!(fwd.contains(&expected_node));
}
for expected_node in expected_bwd {
println!("expected_node: {}", &expected_node);
assert!(bwd.contains(&expected_node));
}
};
fwd_bckwd_check(
"cake",
vec!["eggs", "grain", "soil", "chickens", "water", "flour"],
vec![],
);
fwd_bckwd_check(
"eggs",
vec!["chickens", "grain", "soil", "water"],
vec!["cake"],
);
fwd_bckwd_check("flour", vec!["grain", "soil", "water"], vec!["cake"]);
fwd_bckwd_check(
"chickens",
vec!["grain", "soil", "water"],
vec!["cake", "eggs"],
);
fwd_bckwd_check(
"grain",
vec!["water", "soil"],
vec!["cake", "eggs", "flour", "chickens"],
);
fwd_bckwd_check(
"soil",
vec![],
vec!["grain", "eggs", "flour", "cake", "chickens"],
);
fwd_bckwd_check(
"water",
vec![],
vec!["grain", "eggs", "flour", "cake", "chickens"],
);
}
}