utils/grid.rs
1//! Grid helpers.
2
3use crate::input::InputError;
4use std::error::Error;
5use std::hint::cold_path;
6
7/// A parsed grid: `(number of rows, number of columns, data)`.
8pub type Grid<T> = (usize, usize, Vec<T>);
9
10/// Parse a 2D grid.
11///
12/// This function assumes that each byte represents one item in the grid.
13/// Using 1 byte wide output types is recommended to enable more efficient vectorization.
14///
15/// Parsing is done in two passes per line:
16///
17/// 1. A "hot" pass, where each byte is mapped to an output value and checked for validity.
18/// This uses the `hot_map` and `hot_valid` functions, which should be pure and total mappings
19/// from [`u8`] to their respective outputs to enable vectorization.
20/// Additionally, `hot_valid` must return false for both `'\r'` and `'\n'`.
21///
22/// 2. A "slow" pass, where any invalid bytes from the first pass are re-processed with their index
23/// in the final grid.
24/// This uses the `slow_map` function which returns a [`Result`], containing either a mapped
25/// value or an error.
26/// `slow_map` can be used to perform more complex, non-pure, mappings such as storing positions
27/// and will never be called for newlines or bytes that were valid in the first pass.
28/// If all the bytes were valid in the first pass, this pass is skipped.
29///
30/// `default_value` is used to initialize the grid before parsing and for any padding.
31/// A value with an all-zero bit pattern will usually faster to initialize and is recommended when
32/// padding is not used.
33///
34/// Returns (number of rows, number of columns, data) on success.
35///
36/// # Examples
37///
38/// ```
39/// # use utils::grid;
40/// assert_eq!(
41/// grid::parse(
42/// /* input */ "##.#\n#..#\n#.##",
43/// /* padding */ 0,
44/// /* default_value */ false,
45/// /* hot_map */ |b| b == b'#',
46/// /* hot_valid */ |b| matches!(b, b'.' | b'#'),
47/// /* slow_map */ |_, _| Err("expected '.' or '#'"),
48/// ).unwrap(),
49/// (3, 4, vec![
50/// true, true, false, true,
51/// true, false, false, true,
52/// true, false, true, true,
53/// ]),
54/// );
55/// ```
56///
57///
58/// ```
59/// # use utils::grid;
60/// assert_eq!(
61/// grid::parse(
62/// /* input */"##.#\n#..#\n#.##",
63/// /* padding */ 2,
64/// /* default_value */ false,
65/// /* hot_map */ |b| b == b'#',
66/// /* hot_valid */ |b| matches!(b, b'.' | b'#'),
67/// /* slow_map */ |_, _| Err("expected '.' or '#'"),
68/// ).unwrap(),
69/// (7, 8, vec![
70/// false, false, false, false, false, false, false, false,
71/// false, false, false, false, false, false, false, false,
72/// false, false, true, true, false, true, false, false,
73/// false, false, true, false, false, true, false, false,
74/// false, false, true, false, true, true, false, false,
75/// false, false, false, false, false, false, false, false,
76/// false, false, false, false, false, false, false, false,
77/// ]),
78/// );
79/// ```
80///
81/// ```
82/// # use utils::grid;
83/// let mut start = None;
84/// assert_eq!(
85/// grid::parse(
86/// /* input */ ".0.#S\r\n..1..\r\n.###2\r\n.3...",
87/// /* padding */ 1,
88/// /* default_value */ b'#',
89/// /* hot_map */ |b| b,
90/// /* hot_valid */ |b| matches!(b, b'.' | b'#' | b'0'..=b'9'),
91/// /* slow_map */ |i, b| {
92/// match b {
93/// b'S' if start.is_none() => {
94/// start = Some(i);
95/// Ok(b'.')
96/// },
97/// b'S' => Err("expected only one 'S'"),
98/// _ => Err("expected '.', '#', 'S' or a digit")
99/// }
100/// },
101/// ).unwrap(),
102/// (6, 7, vec![
103/// b'#', b'#', b'#', b'#', b'#', b'#', b'#',
104/// b'#', b'.', b'0', b'.', b'#', b'.', b'#',
105/// b'#', b'.', b'.', b'1', b'.', b'.', b'#',
106/// b'#', b'.', b'#', b'#', b'#', b'2', b'#',
107/// b'#', b'.', b'3', b'.', b'.', b'.', b'#',
108/// b'#', b'#', b'#', b'#', b'#', b'#', b'#',
109/// ]),
110/// );
111/// assert_eq!(start, Some(12));
112/// ```
113#[inline]
114pub fn parse<T: Clone, E: Into<Box<dyn Error>>>(
115 input: &str,
116 padding: usize,
117 default_value: T,
118 hot_map: impl Fn(u8) -> T,
119 hot_valid: impl Fn(u8) -> bool,
120 mut slow_map: impl FnMut(usize, u8) -> Result<T, E>,
121) -> Result<Grid<T>, InputError> {
122 assert!(!hot_valid(b'\n'));
123 assert!(!hot_valid(b'\r'));
124 let g = parse_grid_shape(input)?;
125 let padded_cols = g.cols + 2 * padding;
126 let padded_rows = g.rows + 2 * padding;
127
128 let mut data = vec![default_value; padded_cols * padded_rows];
129 for ((r, input_line), data_line) in input
130 .as_bytes()
131 .chunks(g.line_length)
132 .enumerate()
133 .zip(data.chunks_exact_mut(padded_cols).skip(padding))
134 {
135 // Hot pass
136 let mut valid = true;
137 for (&b, d) in input_line[..g.cols]
138 .iter()
139 .zip(data_line[padding..].iter_mut())
140 {
141 *d = hot_map(b);
142 valid &= hot_valid(b);
143 }
144
145 // Slow pass
146 if !valid {
147 for ((c, &b), d) in input_line[..g.cols]
148 .iter()
149 .enumerate()
150 .zip(data_line[padding..].iter_mut())
151 {
152 if hot_valid(b) {
153 continue;
154 }
155 if b == b'\n' || b == b'\r' {
156 return Err(InputError::new(
157 input,
158 &input_line[c..],
159 format!("expected {} columns", g.cols),
160 ));
161 }
162
163 let index = (r + padding) * padded_cols + padding + c;
164 match slow_map(index, b) {
165 Ok(v) => *d = v,
166 Err(err) => return Err(InputError::new(input, &input_line[c..], err)),
167 }
168 }
169 }
170
171 // Check input_line ends in the expected newline.
172 // Do this after the slow pass, so any earlier newlines earlier in the chunk have already
173 // been caught.
174 check_newline(input, &g, r, input_line)?;
175 }
176
177 Ok((padded_rows, padded_cols, data))
178}
179
180#[derive(Clone, Copy)]
181struct GridShape {
182 cols: usize,
183 rows: usize,
184 line_length: usize,
185 crlf_endings: bool,
186}
187
188#[inline]
189fn parse_grid_shape(input: &str) -> Result<GridShape, InputError> {
190 let Some(line_length) = input
191 .bytes()
192 .position(|b| b == b'\n')
193 .map(|x| x + 1)
194 .filter(|&x| x >= 2)
195 else {
196 return Err(InputError::new(input, 0, "expected grid"));
197 };
198
199 let crlf_endings = input.as_bytes()[line_length - 2] == b'\r';
200 let newline_length = 1 + usize::from(crlf_endings);
201 if !(input.len() + newline_length).is_multiple_of(line_length) {
202 return Err(InputError::new(
203 input,
204 input.len(),
205 "expected input length to be a multiple of the first line length",
206 ));
207 }
208
209 Ok(GridShape {
210 cols: line_length - newline_length,
211 rows: (input.len() + newline_length) / line_length,
212 line_length,
213 crlf_endings,
214 })
215}
216
217#[inline]
218fn check_newline(
219 input: &str,
220 parsed_grid: &GridShape,
221 row: usize,
222 row_input: &[u8],
223) -> Result<(), InputError> {
224 if row != parsed_grid.rows - 1
225 && if parsed_grid.crlf_endings {
226 row_input.last_chunk() != Some(b"\r\n")
227 } else {
228 row_input.last_chunk() != Some(b"\n")
229 }
230 {
231 cold_path();
232 return Err(InputError::new(
233 input,
234 &row_input[row_input.len() - 2..],
235 "expected newline",
236 ));
237 }
238 Ok(())
239}
240
241/// Iterate over each row of a 2D grid and pass it to a callback after validation.
242///
243/// Unlike [`parse`], this does not support padding or allocate a transformed grid. The callback
244/// receives the row index, column count and row input bytes. The callback may be called with one
245/// or more rows before an error is found in a later row.
246///
247/// # Examples
248///
249/// ```
250/// # use utils::{grid, input::InputError};
251/// let mut row_masks = Vec::new();
252/// let input = "#.#.#\n.#.#.\n#####";
253/// let shape = grid::for_each_row(
254/// input,
255/// |b| matches!(b, b'.' | b'#'),
256/// || "expected '.' or '#'",
257/// |row, cols, row_bytes| {
258/// if row == 0 && cols > 32 {
259/// return Err(InputError::new(input, 0, "expected at most 32 columns"));
260/// }
261/// let mut bits = 0u32;
262/// for (col, &b) in row_bytes.iter().enumerate() {
263/// bits |= u32::from(b == b'#') << col;
264/// }
265/// row_masks.push(bits);
266/// Ok(())
267/// },
268/// ).unwrap();
269/// assert_eq!(shape, (3, 5));
270/// assert_eq!(row_masks, vec![0b10101, 0b01010, 0b11111]);
271/// ```
272#[inline]
273pub fn for_each_row<E: Into<Box<dyn Error>>>(
274 input: &str,
275 hot_valid: impl Fn(u8) -> bool,
276 invalid_error: impl Fn() -> E,
277 mut callback: impl FnMut(usize, usize, &[u8]) -> Result<(), InputError>,
278) -> Result<(usize, usize), InputError> {
279 assert!(!hot_valid(b'\n'));
280 assert!(!hot_valid(b'\r'));
281 let g = parse_grid_shape(input)?;
282
283 for (r, input_line) in input.as_bytes().chunks(g.line_length).enumerate() {
284 let mut all_valid = true;
285 for &b in &input_line[..g.cols] {
286 all_valid &= hot_valid(b);
287 }
288
289 if !all_valid {
290 cold_path();
291 for (c, &b) in input_line[..g.cols].iter().enumerate() {
292 if !hot_valid(b) {
293 return Err(InputError::new(input, &input_line[c..], invalid_error()));
294 }
295 }
296 unreachable!();
297 }
298
299 check_newline(input, &g, r, input_line)?;
300
301 callback(r, g.cols, &input_line[..g.cols])?;
302 }
303
304 Ok((g.rows, g.cols))
305}
306
307/// Parse a "standard" maze with open tiles `.`, walls `#` and one start `S` and one end `E`.
308///
309/// Returns ((number of rows, number of columns, data), start index, end index) on success.
310///
311/// # Examples
312/// ```
313/// # use utils::grid;
314/// assert_eq!(
315/// grid::parse_maze("...#S\n.#E#.\n.###.\n.....", 1).unwrap(),
316/// (
317/// (6, 7, vec![
318/// b'#', b'#', b'#', b'#', b'#', b'#', b'#',
319/// b'#', b'.', b'.', b'.', b'#', b'.', b'#',
320/// b'#', b'.', b'#', b'.', b'#', b'.', b'#',
321/// b'#', b'.', b'#', b'#', b'#', b'.', b'#',
322/// b'#', b'.', b'.', b'.', b'.', b'.', b'#',
323/// b'#', b'#', b'#', b'#', b'#', b'#', b'#',
324/// ]),
325/// 12,
326/// 17,
327/// )
328/// );
329/// ```
330#[inline]
331pub fn parse_maze(input: &str, padding: usize) -> Result<(Grid<u8>, usize, usize), InputError> {
332 let mut start = None;
333 let mut end = None;
334 let grid = parse(
335 input,
336 padding,
337 if padding > 0 { b'#' } else { 0 },
338 |b| b,
339 |b| matches!(b, b'.' | b'#'),
340 |i, b| {
341 match b {
342 b'S' if start.is_none() => start = Some(i),
343 b'S' => return Err("expected one 'S'"),
344 b'E' if end.is_none() => end = Some(i),
345 b'E' => return Err("expected one 'E'"),
346 _ => return Err("expected '.', '#', 'S' or 'E'"),
347 }
348 Ok(b'.')
349 },
350 )?;
351 let Some(start) = start else {
352 return Err(InputError::new(input, 0, "expected one 'S'"));
353 };
354 let Some(end) = end else {
355 return Err(InputError::new(input, 0, "expected one 'E'"));
356 };
357 Ok((grid, start, end))
358}
359
360/// Checks that the provided grid has walls on each edge.
361///
362/// # Examples
363/// ```
364/// # use utils::grid::is_enclosed;
365/// assert_eq!(
366/// is_enclosed(5, 6, &[
367/// b'#', b'#', b'#', b'#', b'#', b'#',
368/// b'#', b'.', b'.', b'.', b'.', b'#',
369/// b'#', b'.', b'.', b'.', b'.', b'#',
370/// b'#', b'.', b'.', b'.', b'.', b'#',
371/// b'#', b'#', b'#', b'#', b'#', b'#',
372/// ], |&b| b == b'#'),
373/// true,
374/// );
375/// assert_eq!(
376/// is_enclosed(5, 6, &[
377/// b'#', b'#', b'#', b'#', b'#', b'#',
378/// b'#', b'.', b'.', b'.', b'.', b'#',
379/// b'#', b'.', b'.', b'.', b'.', b'#',
380/// b'#', b'.', b'.', b'.', b'.', b'.',
381/// b'#', b'#', b'#', b'#', b'#', b'#',
382/// ], |&b| b == b'#'),
383/// false,
384/// );
385/// ```
386pub fn is_enclosed<T>(rows: usize, cols: usize, grid: &[T], is_wall: impl Fn(&T) -> bool) -> bool {
387 grid[..cols].iter().all(&is_wall)
388 && grid[(rows - 1) * cols..].iter().all(&is_wall)
389 && (1..rows).all(|r| is_wall(&grid[r * cols]) && is_wall(&grid[(r + 1) * cols - 1]))
390}