1use std::io::{self, Write};
7
8pub fn write_grid<W: Write>(out: &mut W, width: u32, height: u32, cells: &[u8]) -> io::Result<()> {
15 writeln!(out, "# grid {width}x{height}")?;
16 for row in cells.chunks(width as usize) {
17 let line = row.iter().map(|c| c.to_string()).collect::<Vec<_>>().join(",");
18 writeln!(out, "{line}")?;
19 }
20 Ok(())
21}
22
23pub const NO_ROW: u32 = u32::MAX;
25
26fn is_placed(x: f32, y: f32) -> bool {
30 x.is_finite() && y.is_finite()
31}
32
33pub fn write_points<W: Write>(out: &mut W, pos_x: &[f32], pos_y: &[f32], color: Option<&[u8]>) -> io::Result<()> {
42 let placed = pos_x.iter().zip(pos_y).filter(|&(&x, &y)| is_placed(x, y)).count();
43 writeln!(out, "# points {placed}")?;
44 if let Some(color) = color {
45 writeln!(out, "x,y,color")?;
46 for ((&x, &y), c) in pos_x.iter().zip(pos_y).zip(color) {
47 if is_placed(x, y) {
48 writeln!(out, "{x},{y},{c}")?;
49 }
50 }
51 } else {
52 writeln!(out, "x,y")?;
53 for (&x, &y) in pos_x.iter().zip(pos_y) {
54 if is_placed(x, y) {
55 writeln!(out, "{x},{y}")?;
56 }
57 }
58 }
59 Ok(())
60}
61
62pub fn point_rows(pos_x: &[f32], pos_y: &[f32]) -> Vec<u32> {
65 let mut next = 0;
66 pos_x
67 .iter()
68 .zip(pos_y)
69 .map(|(&x, &y)| {
70 if is_placed(x, y) {
71 next += 1;
72 next - 1
73 } else {
74 NO_ROW
75 }
76 })
77 .collect()
78}
79
80pub fn write_edges<W: Write>(out: &mut W, src: &[u32], dst: &[u32], color: &[u8], rows: &[u32]) -> io::Result<()> {
89 let row = |node: u32| rows.get(node as usize).copied().unwrap_or(NO_ROW);
90 let kept = |&(&a, &b): &(&u32, &u32)| row(a) != NO_ROW && row(b) != NO_ROW;
91 let count = src.iter().zip(dst).filter(kept).count();
92 writeln!(out, "# edges {count}")?;
93 writeln!(out, "src,dst,color")?;
94 for (i, pair) in src.iter().zip(dst).enumerate() {
95 if kept(&pair) {
96 let c = color.get(i).copied().unwrap_or(0);
97 writeln!(out, "{},{},{c}", row(*pair.0), row(*pair.1))?;
98 }
99 }
100 Ok(())
101}
102
103#[cfg(test)]
104mod tests {
105 use super::{NO_ROW, point_rows, write_edges, write_grid, write_points};
106
107 fn render(f: impl FnOnce(&mut Vec<u8>)) -> String {
108 let mut buf = Vec::new();
109 f(&mut buf);
110 String::from_utf8(buf).expect("output should be utf8")
111 }
112
113 #[test]
114 fn a_grid_writes_one_line_per_row() {
115 let text = render(|out| write_grid(out, 3, 2, &[0, 1, 2, 3, 4, 5]).expect("write"));
116 assert_eq!(text, "# grid 3x2\n0,1,2\n3,4,5\n");
117 }
118
119 #[test]
120 fn points_gain_a_color_column_only_when_the_lane_exists() {
121 let bare = render(|out| write_points(out, &[0.5, 1.5], &[2.0, 3.0], None).expect("write"));
122 assert_eq!(bare, "# points 2\nx,y\n0.5,2\n1.5,3\n");
123
124 let colored = render(|out| write_points(out, &[0.5], &[2.0], Some(&[7])).expect("write"));
125 assert_eq!(colored, "# points 1\nx,y,color\n0.5,2,7\n");
126 }
127
128 #[test]
129 fn edges_write_their_endpoints_and_colours() {
130 let rows = [0, 1, 2, 3];
131 let text = render(|out| write_edges(out, &[0, 2], &[1, 3], &[0, 5], &rows).expect("write"));
132 assert_eq!(text, "# edges 2\nsrc,dst,color\n0,1,0\n2,3,5\n");
133 }
134
135 #[test]
137 fn edges_without_colours_fall_back_to_zero() {
138 let rows: Vec<u32> = (0..9).collect();
139 let text = render(|out| write_edges(out, &[7], &[8], &[], &rows).expect("write"));
140 assert_eq!(text, "# edges 1\nsrc,dst,color\n7,8,0\n");
141 }
142
143 #[test]
145 fn a_retired_node_is_left_out_and_the_rows_close_up() {
146 let (xs, ys) = ([0.5, f32::NAN, 2.5], [1.0, f32::NAN, 3.0]);
147 let text = render(|out| write_points(out, &xs, &ys, Some(&[1, 2, 3])).expect("write"));
148 assert_eq!(text, "# points 2\nx,y,color\n0.5,1,1\n2.5,3,3\n");
149
150 let rows = point_rows(&xs, &ys);
151 assert_eq!(rows, [0, NO_ROW, 1]);
152 let text = render(|out| write_edges(out, &[0, 0], &[2, 1], &[4, 6], &rows).expect("write"));
153 assert_eq!(
154 text, "# edges 1\nsrc,dst,color\n0,1,4\n",
155 "node 2 is row 1 now, and 1 is gone"
156 );
157 }
158}