utils/grid.rs
1//! Grid helpers.
2
3use crate::input::InputError;
4
5/// Parse 2D grid.
6///
7/// This function assumes that one byte represents each item in the grid.
8///
9/// Returns (number of rows, number of columns, data) on success.
10///
11/// # Examples
12///
13/// ```
14/// # use utils::grid::from_str;
15/// assert_eq!(
16/// from_str("##.#\n#..#\n#.##", |c| match c {
17/// b'#' => Some(true),
18/// b'.' => Some(false),
19/// _ => None,
20/// }).unwrap(),
21/// (3, 4, vec![
22/// true, true, false, true,
23/// true, false, false, true,
24/// true, false, true, true,
25/// ]),
26/// );
27/// ```
28pub fn from_str<T>(
29 input: &str,
30 mut func: impl FnMut(u8) -> Option<T>,
31) -> Result<(usize, usize, Vec<T>), InputError> {
32 let mut data = Vec::with_capacity(input.len());
33 let mut lines = input.lines().peekable();
34
35 let Some(&first_line) = lines.peek() else {
36 return Err(InputError::new(input, input, "expected grid"));
37 };
38
39 let columns = first_line.len().max(1);
40 for line in lines {
41 if line.len() != columns {
42 return Err(InputError::new(
43 input,
44 line,
45 format!("expected {columns} column(s)"),
46 ));
47 }
48
49 for b in line.bytes() {
50 if let Some(v) = func(b) {
51 data.push(v);
52 } else {
53 return Err(InputError::new(input, b as char, "invalid character"));
54 }
55 }
56 }
57
58 let rows = data.len() / columns;
59 debug_assert_eq!(rows * columns, data.len());
60
61 Ok((rows, columns, data))
62}
63
64/// Parse 2D grid, adding padding around the edges.
65///
66/// Similar to [`from_str`], but pads the edges of the parsed grid with `padding` rows and columns
67/// filled with the default value. This is helpful to avoid bounds checks when considering a
68/// location's neighbors.
69///
70/// Returns (number of rows, number of columns, data) on success (row and column counts include
71/// the added padding).
72///
73/// # Examples
74///
75/// ```
76/// # use utils::grid::from_str_padded;
77/// assert_eq!(
78/// from_str_padded("##.#\n#..#\n#.##", 2, false, |c| match c {
79/// b'#' => Some(true),
80/// b'.' => Some(false),
81/// _ => None,
82/// }).unwrap(),
83/// (7, 8, vec![
84/// false, false, false, false, false, false, false, false,
85/// false, false, false, false, false, false, false, false,
86/// false, false, true, true, false, true, false, false,
87/// false, false, true, false, false, true, false, false,
88/// false, false, true, false, true, true, false, false,
89/// false, false, false, false, false, false, false, false,
90/// false, false, false, false, false, false, false, false,
91/// ]),
92/// );
93/// ```
94pub fn from_str_padded<T: Clone>(
95 input: &str,
96 padding: usize,
97 padding_value: T,
98 mut func: impl FnMut(u8) -> Option<T>,
99) -> Result<(usize, usize, Vec<T>), InputError> {
100 let mut data = Vec::with_capacity(input.len());
101 let mut lines = input.lines().peekable();
102
103 let Some(&first_line) = lines.peek() else {
104 return Err(InputError::new(input, input, "expected grid"));
105 };
106
107 let columns = first_line.len().max(1);
108 let padded_columns = columns + 2 * padding;
109
110 // Add initial padding rows + padding for start of first actual row
111 data.resize(padded_columns * padding + padding, padding_value.clone());
112
113 for line in lines {
114 if line.len() != columns {
115 return Err(InputError::new(
116 input,
117 line,
118 format!("expected {columns} column(s)"),
119 ));
120 }
121
122 for b in line.bytes() {
123 if let Some(v) = func(b) {
124 data.push(v);
125 } else {
126 return Err(InputError::new(input, b as char, "invalid character"));
127 }
128 }
129
130 // Add padding for the end of the current row, and the start of the next row
131 data.resize(data.len() + 2 * padding, padding_value.clone());
132 }
133
134 // Add final padding rows, minus the already added padding for the start of a row
135 data.resize(
136 data.len() + padded_columns * padding - padding,
137 padding_value,
138 );
139
140 let rows = data.len() / padded_columns;
141 debug_assert_eq!(rows * padded_columns, data.len());
142
143 Ok((rows, padded_columns, data))
144}
145
146/// Checks that the provided grid has walls on each edge.
147///
148/// # Examples
149/// ```
150/// # use utils::grid::is_enclosed;
151/// assert_eq!(
152/// is_enclosed(5, 6, &[
153/// b'#', b'#', b'#', b'#', b'#', b'#',
154/// b'#', b'.', b'.', b'.', b'.', b'#',
155/// b'#', b'.', b'.', b'.', b'.', b'#',
156/// b'#', b'.', b'.', b'.', b'.', b'#',
157/// b'#', b'#', b'#', b'#', b'#', b'#',
158/// ], |&b| b == b'#'),
159/// true,
160/// );
161/// assert_eq!(
162/// is_enclosed(5, 6, &[
163/// b'#', b'#', b'#', b'#', b'#', b'#',
164/// b'#', b'.', b'.', b'.', b'.', b'#',
165/// b'#', b'.', b'.', b'.', b'.', b'#',
166/// b'#', b'.', b'.', b'.', b'.', b'.',
167/// b'#', b'#', b'#', b'#', b'#', b'#',
168/// ], |&b| b == b'#'),
169/// false,
170/// );
171/// ```
172pub fn is_enclosed<T>(rows: usize, cols: usize, grid: &[T], is_wall: impl Fn(&T) -> bool) -> bool {
173 grid[..cols].iter().all(&is_wall)
174 && grid[(rows - 1) * cols..].iter().all(&is_wall)
175 && (1..rows).all(|r| is_wall(&grid[r * cols]) && is_wall(&grid[(r + 1) * cols - 1]))
176}