draw_dag/
lib.rs

1#![feature(let_chains)]
2#![feature(iter_intersperse)]
3#![warn(clippy::pedantic)]
4
5use std::cmp::Ordering;
6use std::collections::BTreeMap;
7use std::fmt::Display;
8use std::iter::repeat;
9use std::ops::Bound::{Excluded, Included};
10
11/// Trait to implement for nodes.
12pub trait Graph: Sized {
13    fn next(&self) -> &[Self];
14}
15
16#[derive(Debug, Eq, PartialEq, Clone, Copy)]
17struct Coordinate {
18    x: usize,
19    y: usize,
20}
21
22impl Ord for Coordinate {
23    fn cmp(&self, other: &Self) -> Ordering {
24        match self.y.cmp(&other.y) {
25            Ordering::Equal => self.x.cmp(&other.x),
26            ord @ (Ordering::Less | Ordering::Greater) => ord,
27        }
28    }
29}
30
31impl PartialOrd for Coordinate {
32    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
33        Some(self.cmp(other))
34    }
35}
36
37/// Draws the graph.
38pub fn draw_dag<T: Graph + Display + Copy>(node: T, width_spacing: usize) -> String {
39    let mut column = 0;
40
41    let mut prev_depth = None;
42    let mut coordinates = BTreeMap::<Coordinate, T>::new();
43    let mut stack = vec![(0, node)];
44    while let Some((depth, next)) = stack.pop() {
45        let display = next.to_string();
46
47        // Add width
48        let width = display.chars().count();
49
50        if let Some(pd) = prev_depth
51            && depth <= pd
52        {
53            column += width_spacing + width + 1;
54        }
55        prev_depth = Some(depth);
56
57        // Add coordinates of the node
58        coordinates.insert(
59            Coordinate {
60                x: column,
61                y: depth,
62            },
63            next,
64        );
65
66        // Add new nodes to stack.
67        stack.extend(next.next().iter().copied().map(|n| (depth + 1, n)));
68    }
69
70    let mut output = String::new();
71    let mut row = 0;
72    let mut column = 0;
73    for (Coordinate { x, y }, node) in &coordinates {
74        let row_diff = y - row;
75        if row_diff > 0 {
76            column = 0;
77
78            let mut prev_iter = coordinates
79                .range((
80                    Included(Coordinate { x: 0, y: *y - 1 }),
81                    Excluded(Coordinate { x: 0, y: *y }),
82                ))
83                .map(|(coord, _)| coord)
84                .copied()
85                .peekable();
86            output.push('\n');
87            let mut last = 0;
88            while let Some(prev) = prev_iter.next() {
89                let start = Coordinate { x: prev.x, y: *y };
90                let end = match prev_iter.peek() {
91                    Some(Coordinate { x, .. }) => Coordinate { x: *x, y: *y },
92                    None => Coordinate { x: 0, y: *y + 1 },
93                };
94
95                let mut below_iter = coordinates
96                    .range((Included(start), Excluded(end)))
97                    .map(|(coord, _)| coord)
98                    .copied()
99                    .peekable();
100
101                if let Some(first) = below_iter.next() {
102                    output.extend(repeat(' ').take(prev.x - last));
103
104                    if let Some(second) = below_iter.peek() {
105                        debug_assert!(second.y == first.y);
106
107                        output.push('├');
108                        output.extend(repeat('─').take(second.x - first.x - 1));
109
110                        while let Some(first_following) = below_iter.next() {
111                            if let Some(second_following) = below_iter.peek() {
112                                output.push('┬');
113                                output.extend(
114                                    repeat('─').take(second_following.x - first_following.x - 1),
115                                );
116                            } else {
117                                output.push('┐');
118                                last = first_following.x + 1;
119                            }
120                        }
121                    } else {
122                        output.push('│');
123                        last = first.x + 1;
124                    }
125                }
126            }
127            output.push('\n');
128        }
129        row = *y;
130
131        let column_diff = x - column;
132        output.extend(repeat(' ').take(column_diff));
133        column = *x;
134
135        let display = node.to_string();
136        column += display.chars().count();
137        output.push_str(&display);
138    }
139    output
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use std::alloc::{alloc, Layout};
146    use std::ptr;
147    use std::ptr::NonNull;
148
149    #[derive(Debug, Clone, Copy)]
150    struct OuterTestNode(NonNull<InnerTestNode>);
151    impl std::fmt::Display for OuterTestNode {
152        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
153            write!(f, "{}", unsafe { self.0.as_ref() })
154        }
155    }
156    impl Graph for OuterTestNode {
157        fn next(&self) -> &[Self] {
158            unsafe { self.0.as_ref().next.as_slice() }
159        }
160    }
161
162    #[derive(Debug)]
163    struct InnerTestNode {
164        next: Vec<OuterTestNode>,
165        value: usize,
166    }
167    impl std::fmt::Display for InnerTestNode {
168        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
169            write!(f, "{}", self.value)
170        }
171    }
172
173    fn allocate(value: usize, next: Vec<OuterTestNode>) -> OuterTestNode {
174        unsafe {
175            let ptr = alloc(Layout::new::<InnerTestNode>()).cast();
176            ptr::write(ptr, InnerTestNode { next, value });
177            OuterTestNode(NonNull::new(ptr).unwrap())
178        }
179    }
180    #[test]
181    fn first() {
182        let a = allocate(2, Vec::new());
183        let b = allocate(1, vec![a]);
184
185        let graph = draw_dag(b, 1);
186        assert_eq!(
187            graph,
188            "\
189            1\n\
190            │\n\
191            2\
192        "
193        );
194    }
195
196    #[test]
197    fn second() {
198        let a = allocate(15, Vec::new());
199        let b = allocate(14, Vec::new());
200        let c = allocate(13, Vec::new());
201        let d = allocate(12, vec![a]);
202        let e = allocate(121, vec![c, b]);
203        let f = allocate(10, vec![d, e]);
204
205        let graph = draw_dag(f, 1);
206        assert_eq!(
207            graph,
208            "\
209            10\n\
210            ├───────┐\n\
211            121     12\n\
212            ├───┐   │\n\
213            14  13  15\
214        "
215        );
216    }
217    #[test]
218    fn three() {
219        let a = allocate(1, Vec::new());
220        let b = allocate(2, Vec::new());
221        let c = allocate(3, Vec::new());
222        let d = allocate(4, Vec::new());
223        let e = allocate(5, vec![a]);
224        let f = allocate(6, Vec::new());
225        let g = allocate(7, vec![b]);
226        let h = allocate(8, vec![d, c]);
227        let i = allocate(9, vec![e]);
228        let j = allocate(10, vec![h, g, f]);
229        let k = allocate(11, vec![j, i]);
230        let l = allocate(12, vec![k]);
231
232        let graph = draw_dag(l, 1);
233        assert_eq!(
234            graph,
235            "\
236            12\n\
237            │\n\
238            11\n\
239            ├───┐\n\
240            9   10\n\
241            │   ├──┬──┐\n\
242            5   6  7  8\n\
243            │      │  ├──┐\n\
244            1      2  3  4\
245        "
246        );
247    }
248}