#[derive(Debug, PartialEq)]
pub struct Node<Id, Item>
where
Id: Copy + Eq,
{
pub id: Id,
pub deps: Vec<Id>,
pub value: Item,
}
impl<Id, Item> Node<Id, Item>
where
Id: Copy + Eq,
{
pub fn new(id: Id, deps: Vec<Id>, value: Item) -> Self {
Self { id, deps, value }
}
}
#[derive(PartialEq, Debug)]
pub enum TopsortError<Id> {
TargetNotFound(Id),
CyclicDependency(Id),
}
fn find_index<Id, Item>(domain: &[Node<Id, Item>], target: Id) -> Result<usize, TopsortError<Id>>
where
Id: Copy + Eq,
{
match domain.iter().position(|node| node.id == target) {
Some(index) => Ok(index),
None => Err(TopsortError::TargetNotFound(target)),
}
}
fn visit<Id, Item, F>(
domain: &[Node<Id, Item>],
target: Id,
cb: &mut F,
visited: &mut Vec<bool>,
current_path: &mut Vec<Id>,
) -> Result<(), TopsortError<Id>>
where
Id: Copy + Eq,
F: FnMut(&Node<Id, Item>),
{
let index = find_index(domain, target)?;
if visited[index] {
return Ok(());
}
if current_path.contains(&target) {
return Err(TopsortError::CyclicDependency(target));
}
current_path.push(target);
for dep in domain[index].deps.iter() {
visit(domain, *dep, cb, visited, current_path)?;
}
cb(&domain[index]);
visited[index] = true;
current_path.pop();
Ok(())
}
pub fn sort_cb<Id, Item, F>(
domain: &[Node<Id, Item>],
target: Id,
cb: &mut F,
) -> Result<(), TopsortError<Id>>
where
Id: Copy + Eq,
F: FnMut(&Node<Id, Item>),
{
let size = domain.into_iter().size_hint().0;
let mut visited: Vec<bool> = Vec::with_capacity(size);
visited.resize(size, false);
let mut current_path: Vec<Id> = Vec::new();
visit(domain, target, cb, &mut visited, &mut current_path)
}
pub fn sort<Id, Item>(domain: &[Node<Id, Item>], target: Id) -> Result<Vec<Item>, TopsortError<Id>>
where
Id: Copy + Eq,
Item: Clone,
{
let mut out = Vec::new();
sort_cb(domain, target, &mut |node: &Node<_, _>| {
out.push(node.value.clone());
})?;
Ok(out)
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn sort_cb_works() {
let mut out = Vec::new();
let result = sort_cb(
&[
Node::new(1, vec![2, 3], "hello"),
Node::new(2, vec![], "world"),
Node::new(3, vec![2], "cat"),
],
1,
&mut |node: &Node<_, _>| {
out.push(node.value);
},
);
assert_eq!(result, Ok(()));
assert_eq!(out, vec!["world", "cat", "hello"]);
}
#[test]
fn sort_works() {
let result = sort(
&vec![
Node::new(1, vec![2, 3], "hello"),
Node::new(2, vec![], "world"),
Node::new(3, vec![2], "cat"),
],
1,
);
assert_eq!(result, Ok(vec!["world", "cat", "hello"]));
}
#[test]
fn target_not_found() {
let result = sort(
&vec![
Node::new(1, vec![2, 3], "hello"),
Node::new(2, vec![], "world"),
Node::new(3, vec![2, 4], "cat"),
],
1,
);
assert_eq!(result, Err(TopsortError::TargetNotFound(4)));
}
#[test]
fn cyclic_dependency() {
let result = sort(
&vec![
Node::new(1, vec![2, 3], "hello"),
Node::new(2, vec![1], "world"),
Node::new(3, vec![2], "cat"),
],
1,
);
assert_eq!(result, Err(TopsortError::CyclicDependency(1)));
}
#[test]
fn empty_domain() {
let result = sort(&[] as &[Node<i32, i32>], 1);
assert_eq!(result, Err(TopsortError::TargetNotFound(1)));
}
}