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 {}