use std::collections::{HashMap, HashSet};
pub type DependencyMap = Vec<(String, Vec<String>)>;
pub fn resolve_order(deps: &DependencyMap) -> anyhow::Result<Vec<String>> {
let mut graph = petgraph::graph::DiGraph::<String, ()>::new();
let mut node_indices: HashMap<String, petgraph::graph::NodeIndex> = HashMap::new();
for (item, _) in deps {
let idx = graph.add_node(item.clone());
node_indices.insert(item.clone(), idx);
}
for (item, references) in deps {
let from_idx = node_indices.get(item).unwrap();
for dep in references {
if let Some(to_idx) = node_indices.get(dep) {
graph.add_edge(*from_idx, *to_idx, ());
}
}
}
let sccs = petgraph::algo::kosaraju_scc(&graph);
let mut scc_id: HashMap<petgraph::graph::NodeIndex, usize> = HashMap::new();
for (i, scc) in sccs.iter().enumerate() {
for &node in scc {
scc_id.insert(node, i);
}
}
let mut scc_deps: Vec<HashSet<usize>> = vec![HashSet::new(); sccs.len()];
for edge in graph.raw_edges() {
let from_scc = scc_id[&edge.source()];
let to_scc = scc_id[&edge.target()];
if from_scc != to_scc {
scc_deps[from_scc].insert(to_scc);
}
}
let mut dep_count: Vec<usize> = sccs
.iter()
.enumerate()
.map(|(i, _)| scc_deps[i].len())
.collect();
let mut order = Vec::new();
let mut queue: Vec<usize> = (0..sccs.len()).filter(|&i| dep_count[i] == 0).collect();
while let Some(scc_idx) = queue.pop() {
for &node in &sccs[scc_idx] {
order.push(graph[node].clone());
}
for other in 0..sccs.len() {
if scc_deps[other].contains(&scc_idx) {
scc_deps[other].remove(&scc_idx);
dep_count[other] -= 1;
if dep_count[other] == 0 {
queue.push(other);
}
}
}
}
let ordered_set: HashSet<String> = order.iter().cloned().collect();
for (item, _) in deps {
if !ordered_set.contains(item) {
order.push(item.clone());
}
}
let all_referenced: HashSet<String> = deps
.iter()
.flat_map(|(_, refs)| refs.iter())
.cloned()
.collect();
let existing: HashSet<String> = order.iter().cloned().collect();
for item in all_referenced {
if !existing.contains(&item) {
order.push(item);
}
}
Ok(order)
}
pub fn merge_order(auto_order: &[String], user_order: &[String]) -> Vec<String> {
if user_order.is_empty() {
return auto_order.to_vec();
}
let mut result: Vec<String> = user_order.to_vec();
let user_set: HashSet<String> = user_order.iter().cloned().collect();
for item in auto_order {
if !user_set.contains(item) && !result.contains(item) {
result.push(item.clone());
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_simple_dependency() {
let deps = vec![
(
"Observation".to_string(),
vec!["Patient".to_string(), "Encounter".to_string()],
),
("Encounter".to_string(), vec!["Patient".to_string()]),
("Patient".to_string(), vec![]),
];
let order = resolve_order(&deps).expect("should resolve simple dependency order");
let patient_idx = order
.iter()
.position(|r| r == "Patient")
.expect("Patient should be in creation order");
let encounter_idx = order
.iter()
.position(|r| r == "Encounter")
.expect("Encounter should be in creation order");
let observation_idx = order
.iter()
.position(|r| r == "Observation")
.expect("Observation should be in creation order");
assert!(
patient_idx < encounter_idx,
"Patient should come before Encounter"
);
assert!(
patient_idx < observation_idx,
"Patient should come before Observation"
);
assert!(
encounter_idx < observation_idx,
"Encounter should come before Observation"
);
}
#[test]
fn resolve_circular_dependency() {
let deps = vec![
("A".to_string(), vec!["B".to_string()]),
("B".to_string(), vec!["A".to_string()]),
];
let order = resolve_order(&deps).expect("should resolve circular dependency order");
assert_eq!(order.len(), 2);
assert!(order.contains(&"A".to_string()));
assert!(order.contains(&"B".to_string()));
}
#[test]
fn resolve_no_dependencies() {
let deps = vec![("A".to_string(), vec![]), ("B".to_string(), vec![])];
let order = resolve_order(&deps).expect("should resolve order with no dependencies");
assert!(order.contains(&"A".to_string()));
assert!(order.contains(&"B".to_string()));
}
#[test]
fn resolve_chain() {
let deps = vec![
("A".to_string(), vec!["B".to_string()]),
("B".to_string(), vec!["C".to_string()]),
("C".to_string(), vec![]),
];
let order = resolve_order(&deps).expect("should resolve chain");
let c = order.iter().position(|r| r == "C").unwrap();
let b = order.iter().position(|r| r == "B").unwrap();
let a = order.iter().position(|r| r == "A").unwrap();
assert!(c < b, "C before B");
assert!(b < a, "B before A");
}
#[test]
fn merge_order_with_override() {
let auto = vec!["A".to_string(), "B".to_string(), "C".to_string()];
let user = vec!["B".to_string(), "A".to_string()];
let merged = merge_order(&auto, &user);
assert_eq!(merged[0], "B");
assert_eq!(merged[1], "A");
assert_eq!(merged[2], "C");
}
#[test]
fn merge_order_empty_override() {
let auto = vec!["A".to_string(), "B".to_string()];
let user: Vec<String> = vec![];
let merged = merge_order(&auto, &user);
assert_eq!(merged, auto);
}
#[test]
fn resolve_diamond() {
let deps = vec![
("A".to_string(), vec!["B".to_string(), "C".to_string()]),
("B".to_string(), vec!["D".to_string()]),
("C".to_string(), vec!["D".to_string()]),
("D".to_string(), vec![]),
];
let order = resolve_order(&deps).expect("should resolve diamond");
let d = order.iter().position(|r| r == "D").unwrap();
let b = order.iter().position(|r| r == "B").unwrap();
let c = order.iter().position(|r| r == "C").unwrap();
let a = order.iter().position(|r| r == "A").unwrap();
assert!(d < b, "D before B");
assert!(d < c, "D before C");
assert!(b < a, "B before A");
assert!(c < a, "C before A");
}
}