use std::collections::{HashMap, VecDeque};
use super::dag::Dag;
pub fn topological_levels(dag: &Dag) -> anyhow::Result<Vec<Vec<i64>>> {
let mut in_degree: HashMap<i64, usize> = dag
.nodes
.keys()
.map(|&id| (id, dag.nodes[&id].reverse.len()))
.collect();
let mut queue: VecDeque<i64> = in_degree
.iter()
.filter(|(_, d)| **d == 0)
.map(|(id, _)| *id)
.collect();
let mut levels: Vec<Vec<i64>> = Vec::new();
let mut processed = 0usize;
while !queue.is_empty() {
let mut current_level: Vec<i64> = queue.drain(..).collect();
current_level.sort(); processed += current_level.len();
for &id in ¤t_level {
for &fwd in &dag.nodes[&id].forward {
let deg = in_degree.get_mut(&fwd).unwrap();
*deg -= 1;
if *deg == 0 {
queue.push_back(fwd);
}
}
}
levels.push(current_level);
}
if processed < dag.nodes.len() {
let cycle_ids: Vec<i64> = in_degree
.iter()
.filter(|(_, d)| **d > 0)
.map(|(id, _)| *id)
.collect();
anyhow::bail!(
"cycle detected in dependency graph, involves issues: {:?}",
cycle_ids
);
}
Ok(levels)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{Issue, Kind, Priority, Relation, RelationKind, Status};
use crate::planner::dag::Dag;
use chrono::Utc;
fn make_issue(id: i64) -> Issue {
Issue {
id,
parent_id: None,
title: format!("Issue {id}"),
description: String::new(),
status: Status::Todo,
priority: Priority::Medium,
kind: Kind::Task,
assignee: None,
labels: vec![],
files: vec![],
created_at: Utc::now(),
updated_at: Utc::now(),
}
}
fn rel(from: i64, to: i64) -> Relation {
Relation {
id: 0,
from_id: from,
to_id: to,
kind: RelationKind::Blocks,
}
}
#[test]
fn linear_chain() {
let issues: Vec<Issue> = (1..=3).map(make_issue).collect();
let relations = vec![rel(1, 2), rel(2, 3)];
let dag = Dag::build(&issues, &relations);
let levels = topological_levels(&dag).unwrap();
assert_eq!(levels.len(), 3);
assert_eq!(levels[0], vec![1]);
assert_eq!(levels[1], vec![2]);
assert_eq!(levels[2], vec![3]);
}
#[test]
fn parallel_phase() {
let issues: Vec<Issue> = (1..=3).map(make_issue).collect();
let relations = vec![rel(1, 2), rel(1, 3)];
let dag = Dag::build(&issues, &relations);
let levels = topological_levels(&dag).unwrap();
assert_eq!(levels.len(), 2);
assert_eq!(levels[0], vec![1]);
let mut second = levels[1].clone();
second.sort();
assert_eq!(second, vec![2, 3]);
}
#[test]
fn cycle_detection() {
let issues: Vec<Issue> = (1..=2).map(make_issue).collect();
let relations = vec![rel(1, 2), rel(2, 1)];
let dag = Dag::build(&issues, &relations);
assert!(topological_levels(&dag).is_err());
}
}