Skip to main content

szyk/
lib.rs

1//! Generic topological sort algorithm (depth-first)
2//!
3//! # Examples
4//! ```
5//!     use szyk::Node;
6//!     use szyk;
7//!
8//!     let result = szyk::sort(
9//!         &[
10//!             Node::new("wooden pickaxe", vec!["planks", "sticks"], "Pickaxe"),
11//!             Node::new("planks", vec!["wood"], "Planks"),
12//!             Node::new("sticks", vec!["planks"], "Sticks"),
13//!             Node::new("wood", vec![], "Wood"),
14//!         ],
15//!         "wooden pickaxe",
16//!     );
17//!     assert_eq!(result, Ok(vec!["Wood", "Planks", "Sticks", "Pickaxe"]));
18//! ```
19
20#[derive(Debug, PartialEq)]
21pub struct Node<Id, Item>
22where
23    Id: Copy + Eq,
24{
25    /// unique identifier
26    pub id: Id,
27    /// list of dependencies
28    pub deps: Vec<Id>,
29    /// value stored in the node
30    pub value: Item,
31}
32
33impl<Id, Item> Node<Id, Item>
34where
35    Id: Copy + Eq,
36{
37    pub fn new(id: Id, deps: Vec<Id>, value: Item) -> Self {
38        Self { id, deps, value }
39    }
40}
41
42#[derive(PartialEq, Debug)]
43pub enum TopsortError<Id> {
44    /// * `Id` - target that wasn't found
45    TargetNotFound(Id),
46    /// * `Id` - target that depends on itself
47    CyclicDependency(Id),
48}
49
50fn find_index<Id, Item>(domain: &[Node<Id, Item>], target: Id) -> Result<usize, TopsortError<Id>>
51where
52    Id: Copy + Eq,
53{
54    match domain.iter().position(|node| node.id == target) {
55        Some(index) => Ok(index),
56        None => Err(TopsortError::TargetNotFound(target)),
57    }
58}
59
60fn visit<Id, Item, F>(
61    domain: &[Node<Id, Item>],
62    target: Id,
63    cb: &mut F,
64    visited: &mut Vec<bool>,
65    current_path: &mut Vec<Id>,
66) -> Result<(), TopsortError<Id>>
67where
68    Id: Copy + Eq,
69    F: FnMut(&Node<Id, Item>),
70{
71    let index = find_index(domain, target)?;
72
73    if visited[index] {
74        return Ok(());
75    }
76
77    // detect cyclic dependencies
78    if current_path.contains(&target) {
79        return Err(TopsortError::CyclicDependency(target));
80    }
81
82    // push id to the stack
83    current_path.push(target);
84
85    // visit dependencies
86    for dep in domain[index].deps.iter() {
87        visit(domain, *dep, cb, visited, current_path)?;
88    }
89
90    // call callback
91    cb(&domain[index]);
92    visited[index] = true;
93
94    // pop id from the stack
95    current_path.pop();
96    Ok(())
97}
98
99/// calls `cb` with nodes from `domain` in topological order, ending on the node with id of `target`
100///
101/// # Examples:
102/// ```
103///     use szyk::*;
104///
105///     let mut out = Vec::new();
106///     let result = sort_cb(
107///         &[
108///             Node::new("cat", vec!["dog"], "Garfield"),
109///             Node::new("dog", vec![], "Odie"),
110///         ],
111///         "cat",
112///         &mut |node| {
113///             out.push(node.id);
114///         }
115///     );
116///     assert_eq!(result, Ok(()));
117///     assert_eq!(out, vec!["dog", "cat"]);
118/// ```
119pub fn sort_cb<Id, Item, F>(
120    domain: &[Node<Id, Item>],
121    target: Id,
122    cb: &mut F,
123) -> Result<(), TopsortError<Id>>
124where
125    Id: Copy + Eq,
126    F: FnMut(&Node<Id, Item>),
127{
128    let size = domain.into_iter().size_hint().0;
129    let mut visited: Vec<bool> = Vec::with_capacity(size);
130    visited.resize(size, false);
131    let mut current_path: Vec<Id> = Vec::new();
132    visit(domain, target, cb, &mut visited, &mut current_path)
133}
134
135/// returns values of nodes from `domain` in topological order, ending on the node with id of `target`
136///
137/// # Examples:
138/// ```
139///     use szyk::*;
140///
141///     let result = sort(
142///         &[
143///             Node::new("cat", vec!["dog"], "Garfield"),
144///             Node::new("dog", vec![], "Odie"),
145///         ],
146///         "cat",
147///     );
148///     assert_eq!(result, Ok(vec!["Odie", "Garfield"]));
149/// ```
150pub fn sort<Id, Item>(domain: &[Node<Id, Item>], target: Id) -> Result<Vec<Item>, TopsortError<Id>>
151where
152    Id: Copy + Eq,
153    Item: Clone,
154{
155    let mut out = Vec::new();
156    sort_cb(domain, target, &mut |node: &Node<_, _>| {
157        out.push(node.value.clone());
158    })?;
159
160    Ok(out)
161}
162
163#[cfg(test)]
164mod tests {
165    #[allow(unused_imports)]
166    use super::*;
167
168    #[test]
169    fn sort_cb_works() {
170        let mut out = Vec::new();
171        let result = sort_cb(
172            &[
173                Node::new(1, vec![2, 3], "hello"),
174                Node::new(2, vec![], "world"),
175                Node::new(3, vec![2], "cat"),
176            ],
177            1,
178            &mut |node: &Node<_, _>| {
179                out.push(node.value);
180            },
181        );
182        assert_eq!(result, Ok(()));
183        assert_eq!(out, vec!["world", "cat", "hello"]);
184    }
185
186    #[test]
187    fn sort_works() {
188        let result = sort(
189            &vec![
190                Node::new(1, vec![2, 3], "hello"),
191                Node::new(2, vec![], "world"),
192                Node::new(3, vec![2], "cat"),
193            ],
194            1,
195        );
196        assert_eq!(result, Ok(vec!["world", "cat", "hello"]));
197    }
198
199    #[test]
200    fn target_not_found() {
201        let result = sort(
202            &vec![
203                Node::new(1, vec![2, 3], "hello"),
204                Node::new(2, vec![], "world"),
205                Node::new(3, vec![2, 4], "cat"),
206            ],
207            1,
208        );
209        assert_eq!(result, Err(TopsortError::TargetNotFound(4)));
210    }
211
212    #[test]
213    fn cyclic_dependency() {
214        let result = sort(
215            &vec![
216                Node::new(1, vec![2, 3], "hello"),
217                Node::new(2, vec![1], "world"),
218                Node::new(3, vec![2], "cat"),
219            ],
220            1,
221        );
222        assert_eq!(result, Err(TopsortError::CyclicDependency(1)));
223    }
224
225    #[test]
226    fn empty_domain() {
227        let result = sort(&[] as &[Node<i32, i32>], 1);
228        assert_eq!(result, Err(TopsortError::TargetNotFound(1)));
229    }
230}