use std::collections::{HashMap, HashSet, VecDeque};
pub fn topological_sort(items: Vec<(String, Vec<String>)>) -> Result<Vec<String>, Vec<String>> {
let mut item_map: HashMap<String, Vec<String>> = HashMap::new();
let mut item_names = Vec::new();
for (name, deps) in items {
item_names.push(name.clone());
item_map.insert(name, deps);
}
let mut dependents: HashMap<String, Vec<String>> = HashMap::new();
let mut in_degree: HashMap<String, usize> = HashMap::new();
for name in &item_names {
dependents.insert(name.clone(), Vec::new());
in_degree.insert(name.clone(), 0);
}
for name in &item_names {
let deps = item_map.get(name).unwrap();
for dep in deps {
if !item_map.contains_key(dep) {
return Err(vec![format!(
"Item '{}' depends on '{}', but '{}' is not registered",
name, dep, dep
)]);
}
*in_degree.get_mut(name).unwrap() += 1;
dependents.get_mut(dep).unwrap().push(name.clone());
}
}
let mut queue = VecDeque::new();
for (name, degree) in &in_degree {
if *degree == 0 {
queue.push_back(name.clone());
}
}
let mut sorted = Vec::new();
let mut processed = HashSet::new();
while let Some(current) = queue.pop_front() {
if processed.contains(¤t) {
continue;
}
processed.insert(current.clone());
sorted.push(current.clone());
if let Some(deps) = dependents.get(¤t) {
for dependent in deps {
let degree = in_degree.get_mut(dependent).unwrap();
*degree -= 1;
if *degree == 0 {
queue.push_back(dependent.clone());
}
}
}
}
if sorted.len() != item_names.len() {
let remaining: Vec<String> = item_names
.into_iter()
.filter(|name| !processed.contains(name))
.collect();
return Err(remaining);
}
Ok(sorted)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_topological_sort_no_deps() {
let items = vec![
("task-a".to_string(), vec![]),
("task-b".to_string(), vec![]),
("task-c".to_string(), vec![]),
];
let sorted = topological_sort(items).unwrap();
assert_eq!(sorted.len(), 3);
}
#[test]
fn test_topological_sort_with_deps() {
let items = vec![
("task-a".to_string(), vec![]),
("task-b".to_string(), vec!["task-a".to_string()]),
("task-c".to_string(), vec!["task-b".to_string()]),
];
let sorted = topological_sort(items).unwrap();
assert_eq!(sorted, vec!["task-a", "task-b", "task-c"]);
}
#[test]
fn test_topological_sort_multiple_deps() {
let items = vec![
("task-a".to_string(), vec![]),
("task-b".to_string(), vec![]),
(
"task-c".to_string(),
vec!["task-a".to_string(), "task-b".to_string()],
),
];
let sorted = topological_sort(items).unwrap();
assert_eq!(sorted[2], "task-c");
}
#[test]
fn test_topological_sort_cycle() {
let items = vec![
("task-a".to_string(), vec!["task-b".to_string()]),
("task-b".to_string(), vec!["task-a".to_string()]),
];
let result = topological_sort(items);
assert!(result.is_err());
}
#[test]
fn test_topological_sort_missing_dep() {
let items = vec![("task-a".to_string(), vec!["task-b".to_string()])];
let result = topological_sort(items);
assert!(result.is_err());
}
}