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
11pub 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
37pub 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 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 coordinates.insert(
59 Coordinate {
60 x: column,
61 y: depth,
62 },
63 next,
64 );
65
66 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}