cell_grid/lib.rs
1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4//! A simple 2d grid container
5//!
6//! # Example
7//!
8//! ```
9//! use cell_grid::DynamicGrid;
10//!
11//! // Create a new empty grid
12//! let mut grid: DynamicGrid<i32> = DynamicGrid::new();
13//!
14//! // Push rows
15//! grid.push_row([1, 2]).unwrap();
16//! grid.push_row([3, 4]).unwrap();
17//!
18//! // Access by coordinate
19//! assert_eq!(grid.get(0, 0), Some(&1));
20//! assert_eq!(grid.get(1, 0), Some(&2));
21//! assert_eq!(grid.get(0, 1), Some(&3));
22//! assert_eq!(grid.get(1, 1), Some(&4));
23//!
24//! // Iterate the content
25//! assert_eq!(grid.cells().copied().collect::<Vec<_>>(), vec![1, 2, 3, 4]);
26//! assert_eq!(grid.rows().collect::<Vec<_>>(), vec![&[1, 2], &[3, 4]]);
27//! ```
28//!
29//! It is also possible to:
30//!
31//! * Create a grid from size and init function: [`DynamicGrid::new_with`]
32//! * Iterate the cells which overlap a rectangle: [`DynamicGrid::cells_in_rect`]
33//!
34//! ## Features
35//!
36//! * `std`: *(enabled by default)* enable use of the standard library. Must be disabled for `no_std` crates.
37
38extern crate alloc;
39
40#[deprecated(
41 since = "0.1.4",
42 note = "The content of this module has been moved to the crate root"
43)]
44#[doc(hidden)]
45pub mod dynamic;
46mod legacy;
47
48#[allow(deprecated)]
49pub use legacy::{Coord, Grid, Rect};
50
51use core::{fmt::Display, mem};
52
53use alloc::vec::Vec;
54
55/// A row-major 2d grid
56#[derive(Debug, Clone, Eq, PartialEq)]
57pub struct DynamicGrid<T> {
58 cells: Vec<T>,
59 width: usize,
60}
61
62impl<T> Default for DynamicGrid<T> {
63 fn default() -> Self {
64 Self::new()
65 }
66}
67
68impl<T> DynamicGrid<T>
69where
70 T: Default,
71{
72 /// Create a new grid of the given size, with each cells being initialized with the default value of `T`
73 #[must_use]
74 pub fn new_with_default(width: usize, height: usize) -> Self {
75 Self::new_with(width, height, |_, _| T::default())
76 }
77}
78
79impl<T> DynamicGrid<T> {
80 /// Create a new empty grid
81 ///
82 /// Use [`Self::push_row`] to add content
83 #[must_use]
84 pub fn new() -> Self {
85 Self {
86 cells: Vec::new(),
87 width: 0,
88 }
89 }
90
91 /// Create a new empty grid with pre-allocated `capacity` cells
92 ///
93 /// Use [`Self::push_row`] to add content
94 #[must_use]
95 pub fn new_with_capacity(capacity: usize) -> Self {
96 Self {
97 cells: Vec::with_capacity(capacity),
98 width: 0,
99 }
100 }
101
102 /// Create a new grid of the given size, with each cells being initialized with the given function
103 #[must_use]
104 pub fn new_with(width: usize, height: usize, mut init: impl FnMut(usize, usize) -> T) -> Self {
105 Self {
106 cells: (0..(width * height))
107 .map(|i| {
108 let (x, y) = Self::index_to_coord(i, width);
109 init(x, y)
110 })
111 .collect(),
112 width,
113 }
114 }
115
116 /// Create a new grid of the given width and row-major iterator
117 ///
118 ///
119 /// The given iterator must emits cells by row first, then by column
120 ///
121 /// # Errors
122 ///
123 /// Returns [`IncompatibleRowSize`] if the number of elements yielded by the iterator is not compaible
124 /// with the given `width`
125 ///
126 /// # Example
127 ///
128 /// ```
129 /// # use cell_grid::DynamicGrid;
130 /// let grid = DynamicGrid::new_from_iter(2, [1, 2, 3, 4]).unwrap();
131 /// assert_eq!(grid.get(0, 0), Some(&1));
132 /// assert_eq!(grid.get(1, 0), Some(&2));
133 /// assert_eq!(grid.get(0, 1), Some(&3));
134 /// assert_eq!(grid.get(1, 1), Some(&4));
135 /// ```
136 pub fn new_from_iter(
137 width: usize,
138 iter: impl IntoIterator<Item = T>,
139 ) -> Result<Self, IncompatibleRowSize> {
140 let cells: Vec<T> = iter.into_iter().collect();
141 if !cells.is_empty() && (width == 0 || cells.len() % width != 0) {
142 return Err(IncompatibleRowSize);
143 };
144 Ok(Self { cells, width })
145 }
146
147 /// Push a row to the grid
148 ///
149 /// If the grid is not empty, the row length should match the current width of the grid.
150 ///
151 /// # Errors
152 ///
153 /// Returns [`IncompatibleRowSize`] if the grid is not empty and the length of the added row does not match the current width of the grid.
154 pub fn push_row(
155 &mut self,
156 row: impl IntoIterator<Item = T>,
157 ) -> Result<(), IncompatibleRowSize> {
158 let old_len = self.cells.len();
159 self.cells.extend(row);
160 if self.width == 0 {
161 self.width = self.cells.len();
162 } else if self.width != self.cells.len() - old_len {
163 self.cells.truncate(old_len);
164 return Err(IncompatibleRowSize);
165 }
166 Ok(())
167 }
168
169 /// Returns `true` if the grid is empty
170 #[must_use]
171 pub fn is_empty(&self) -> bool {
172 self.cells.is_empty()
173 }
174
175 /// Returns the width of the grid
176 #[must_use]
177 pub fn width(&self) -> usize {
178 self.width
179 }
180
181 /// Returns the height of the grid
182 #[must_use]
183 pub fn height(&self) -> usize {
184 self.cells.len() / self.width
185 }
186
187 /// Get a reference to the cell at col `x` and row `y`
188 ///
189 /// Returns `None` if `x` and `y` are out of bounds
190 #[must_use]
191 pub fn get(&self, x: usize, y: usize) -> Option<&T> {
192 self.index(x, y).and_then(|i| self.cells.get(i))
193 }
194
195 /// Get a mutable reference to the cell at col `x` and row `y`
196 ///
197 /// Returns `None` if `x` and `y` are out of bounds
198 #[must_use]
199 pub fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut T> {
200 self.index(x, y).and_then(|i| self.cells.get_mut(i))
201 }
202
203 /// Set the new value to the cell at col `x` and row `y` and return the old value.
204 ///
205 /// Returns `None` if `x` and `y` are out of bounds
206 pub fn set(&mut self, x: usize, y: usize, mut new_value: T) -> Option<T> {
207 let cell = self.get_mut(x, y)?;
208 mem::swap(cell, &mut new_value);
209 Some(new_value)
210 }
211
212 /// Returns an iterator over the cells
213 #[must_use]
214 pub fn cells(&self) -> impl DoubleEndedIterator<Item = &T> {
215 self.cells.iter()
216 }
217
218 /// Returns a mutable iterator over the cells
219 #[must_use]
220 pub fn cells_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
221 self.cells.iter_mut()
222 }
223
224 /// Returns an iterator over the cells with their corresponding coordinate
225 #[must_use]
226 pub fn cells_with_coords(&self) -> impl DoubleEndedIterator<Item = ((usize, usize), &T)> {
227 self.cells
228 .iter()
229 .enumerate()
230 .map(|(i, cell)| (Self::index_to_coord(i, self.width), cell))
231 }
232
233 /// Returns an mutable iterator over the cells with their corresponding coordinate
234 #[must_use]
235 pub fn cells_with_coords_mut(
236 &mut self,
237 ) -> impl DoubleEndedIterator<Item = ((usize, usize), &mut T)> {
238 self.cells
239 .iter_mut()
240 .enumerate()
241 .map(|(i, cell)| (Self::index_to_coord(i, self.width), cell))
242 }
243
244 /// Returns an iterator over the cells in the rectangle that start at col `x`, row `y` and of size given by `width` and `height`
245 ///
246 /// # Example
247 ///
248 /// ```
249 /// # use cell_grid::DynamicGrid;
250 /// let grid = DynamicGrid::new_with(5, 5, |x, y| (x, y));
251 /// let in_rect: Vec<_> = grid.cells_in_rect(2, 2, 2, 2).copied().collect();
252 /// assert_eq!(in_rect, &[(2, 2), (3, 2), (2, 3), (3, 3)]);
253 /// ```
254 #[must_use]
255 pub fn cells_in_rect(
256 &self,
257 x: usize,
258 y: usize,
259 width: usize,
260 height: usize,
261 ) -> impl DoubleEndedIterator<Item = &T> {
262 let width = width.min(self.width - x);
263 let grid_height = self.height();
264 (y..(y + height))
265 .filter(move |y| *y < grid_height)
266 .filter_map(move |y| self.index(x, y))
267 .flat_map(move |from| &self.cells[from..(from + width)])
268 }
269
270 /// Returns an iterator over the rows
271 #[must_use]
272 pub fn rows(&self) -> impl DoubleEndedIterator<Item = &[T]> {
273 (0..self.cells.len())
274 .step_by(self.width)
275 .map(|i| &self.cells[i..(i + self.width)])
276 }
277
278 fn index(&self, x: usize, y: usize) -> Option<usize> {
279 if x >= self.width {
280 None
281 } else {
282 Some(y * self.width + x)
283 }
284 }
285
286 fn index_to_coord(index: usize, width: usize) -> (usize, usize) {
287 (index % width, index / width)
288 }
289}
290
291/// Error returned by [`DynamicGrid::push_row`] if the length of the row being pushed
292/// is incompatible with the current width of the grid
293#[derive(Debug, Clone)]
294#[non_exhaustive]
295pub struct IncompatibleRowSize;
296
297impl Display for IncompatibleRowSize {
298 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
299 write!(
300 f,
301 "The row size is not compatible with the current content of the grid"
302 )
303 }
304}
305
306#[rustversion::since(1.81)]
307impl core::error::Error for IncompatibleRowSize {}