Skip to main content

aoc_core/
pos.rs

1use derive_more::{Add, AddAssign, Display, Sub, SubAssign};
2
3use crate::dir::Dir;
4
5/// 2-dimensional coordinates struct.
6#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Add, AddAssign, Sub, SubAssign, Display)]
7#[display("{x},{y}")]
8pub struct Pos {
9    /// X coordinate.
10    pub x: isize,
11
12    /// Y coordinate.
13    pub y: isize,
14}
15
16impl Pos {
17    /// Creates a new pos with `x` and `y` coordinates.
18    #[inline]
19    #[must_use]
20    pub const fn new(x: isize, y: isize) -> Self {
21        Self { x, y }
22    }
23
24    /// Returns a new position after movign towards `dir`.
25    #[inline]
26    #[must_use]
27    pub const fn move_dir(&self, dir: Dir) -> Self {
28        match dir {
29            Dir::S => Self::new(self.x + 1, self.y),
30            Dir::E => Self::new(self.x, self.y + 1),
31            Dir::N => Self::new(self.x - 1, self.y),
32            Dir::W => Self::new(self.x, self.y - 1),
33        }
34    }
35
36    /// Changes the position by moving towards `dir`.
37    #[inline]
38    pub const fn move_dir_mut(&mut self, dir: Dir) {
39        match dir {
40            Dir::S => self.x += 1,
41            Dir::E => self.y += 1,
42            Dir::N => self.x -= 1,
43            Dir::W => self.y -= 1,
44        }
45    }
46
47    /// Checks if the position is within a rectangular region's `bounds`
48    /// (two pos representing two diagonally opposite corners).
49    #[inline]
50    #[must_use]
51    pub const fn in_bounds(&self, bounds: (Self, Self)) -> bool {
52        bounds.0.x <= self.x && self.x <= bounds.1.x && bounds.0.y <= self.y && self.y <= bounds.1.y
53    }
54
55    /// Calculates the Manhattan distance to `other`.
56    #[inline]
57    #[must_use]
58    pub const fn manhattan_distance(&self, other: Self) -> usize {
59        self.x.abs_diff(other.x) + self.y.abs_diff(other.y)
60    }
61
62    /// Returns an iterator of adjacent positions.
63    #[inline]
64    pub fn adjacent(&self) -> impl Iterator<Item = Self> + use<> {
65        [
66            Self::new(self.x + 1, self.y),
67            Self::new(self.x, self.y + 1),
68            Self::new(self.x - 1, self.y),
69            Self::new(self.x, self.y - 1),
70        ]
71        .into_iter()
72    }
73
74    /// Returns an iterator of positions in the corners.
75    #[inline]
76    pub fn corners(&self) -> impl Iterator<Item = Self> + use<> {
77        [
78            Self::new(self.x + 1, self.y + 1),
79            Self::new(self.x - 1, self.y + 1),
80            Self::new(self.x - 1, self.y - 1),
81            Self::new(self.x + 1, self.y - 1),
82        ]
83        .into_iter()
84    }
85
86    /// Returns an iterator of both adjacent and corner positions.
87    #[inline]
88    pub fn neighbors(&self) -> impl Iterator<Item = Self> + use<> {
89        self.adjacent().chain(self.corners())
90    }
91}
92
93impl From<(isize, isize)> for Pos {
94    #[inline]
95    fn from(value: (isize, isize)) -> Self {
96        Self::new(value.0, value.1)
97    }
98}
99
100impl From<(usize, usize)> for Pos {
101    #[inline]
102    fn from(value: (usize, usize)) -> Self {
103        #[allow(clippy::cast_possible_wrap)]
104        Self::new(value.0 as isize, value.1 as isize)
105    }
106}
107
108impl From<Pos> for (usize, usize) {
109    #[inline]
110    fn from(val: Pos) -> Self {
111        #[allow(clippy::cast_sign_loss)]
112        (val.x as usize, val.y as usize)
113    }
114}
115
116/// Allows indexing with a [`Pos`] instance.
117pub trait PosGet<V> {
118    /// Returns a reference to the value at `pos`,
119    /// or `None` if the position cannot index the type.
120    fn pos_get(&self, pos: Pos) -> Option<&V>;
121
122    /// Returns a mutable reference to the value at `pos`,
123    /// or `None` if the position cannot index the type.
124    fn pos_get_mut(&mut self, pos: Pos) -> Option<&mut V>;
125}
126
127#[cfg(test)]
128mod tests {
129    use rustc_hash::FxHashSet;
130
131    use super::Pos;
132    use crate::dir::Dir::*;
133
134    // ------------------------------------------------------------------------------------------------
135    // Pos
136
137    #[test]
138    fn new() {
139        let input: (isize, isize) = (1, 2);
140        let expected = (1, 2);
141        let pos = Pos::new(input.0, input.1);
142        let output = (pos.x, pos.y);
143        assert_eq!(expected, output, "\n input: {input:?}");
144    }
145
146    #[test]
147    fn move_dir() {
148        let input = [S, E, N, W];
149        let expected = [
150            Pos::new(2, 2),
151            Pos::new(1, 3),
152            Pos::new(0, 2),
153            Pos::new(1, 1),
154        ];
155        let pos = Pos::new(1, 2);
156        let output = input.map(|d| pos.move_dir(d));
157        assert_eq!(expected, output, "\n input: {input:?}");
158    }
159
160    #[test]
161    fn move_dir_mut() {
162        let input = [
163            (Pos::new(0, 1), S),
164            (Pos::new(1, 1), E),
165            (Pos::new(1, 2), N),
166            (Pos::new(2, 2), W),
167        ];
168        let expected = [
169            (Pos::new(1, 1), S),
170            (Pos::new(1, 2), E),
171            (Pos::new(0, 2), N),
172            (Pos::new(2, 1), W),
173        ];
174        let mut output = input;
175        for (pos, dir) in &mut output {
176            pos.move_dir_mut(*dir);
177        }
178        assert_eq!(expected, output, "\n input: {input:?}");
179    }
180
181    #[test]
182    fn in_bounds() {
183        let input = (
184            (Pos::new(1, 2), Pos::new(4, 7)),
185            [
186                Pos::new(2, 5),
187                Pos::new(1, 5),
188                Pos::new(4, 5),
189                Pos::new(2, 2),
190                Pos::new(2, 7),
191                Pos::new(0, 5),
192                Pos::new(5, 5),
193                Pos::new(2, 1),
194                Pos::new(2, 8),
195            ],
196        );
197        let expected = [true, true, true, true, true, false, false, false, false];
198        let output = input.1.map(|p| p.in_bounds(input.0));
199        assert_eq!(expected, output, "\n input: {input:?}");
200    }
201
202    #[test]
203    fn manhattan_distance() {
204        let input = [
205            (Pos::new(1, 2), Pos::new(3, 4)),
206            (Pos::new(-4, 3), Pos::new(2, -1)),
207            (Pos::new(0, 0), Pos::new(-1, 1)),
208            (Pos::new(4, -3), Pos::new(-2, 2)),
209        ];
210        let expected = [4, 10, 2, 11];
211        let output = input.map(|(p, q)| p.manhattan_distance(q));
212        assert_eq!(expected, output, "\n input: {input:?}");
213    }
214
215    #[test]
216    fn adjacent() {
217        let input = [
218            Pos::new(0, 0),
219            Pos::new(4, 5),
220            Pos::new(-1, 3),
221            Pos::new(0, -2),
222        ];
223        let expected = [
224            FxHashSet::from_iter([
225                Pos::new(1, 0),
226                Pos::new(0, 1),
227                Pos::new(-1, 0),
228                Pos::new(0, -1),
229            ]),
230            FxHashSet::from_iter([
231                Pos::new(5, 5),
232                Pos::new(4, 6),
233                Pos::new(3, 5),
234                Pos::new(4, 4),
235            ]),
236            FxHashSet::from_iter([
237                Pos::new(0, 3),
238                Pos::new(-1, 4),
239                Pos::new(-2, 3),
240                Pos::new(-1, 2),
241            ]),
242            FxHashSet::from_iter([
243                Pos::new(1, -2),
244                Pos::new(0, -1),
245                Pos::new(-1, -2),
246                Pos::new(0, -3),
247            ]),
248        ];
249        let output = input.map(|p| p.adjacent().collect());
250        assert_eq!(expected, output, "\n input: {input:?}");
251    }
252
253    #[test]
254    fn corners() {
255        let input = [
256            Pos::new(0, 0),
257            Pos::new(4, 5),
258            Pos::new(-1, 3),
259            Pos::new(0, -2),
260        ];
261        let expected = [
262            FxHashSet::from_iter([
263                Pos::new(1, 1),
264                Pos::new(-1, 1),
265                Pos::new(-1, -1),
266                Pos::new(1, -1),
267            ]),
268            FxHashSet::from_iter([
269                Pos::new(5, 6),
270                Pos::new(3, 6),
271                Pos::new(3, 4),
272                Pos::new(5, 4),
273            ]),
274            FxHashSet::from_iter([
275                Pos::new(0, 4),
276                Pos::new(-2, 4),
277                Pos::new(-2, 2),
278                Pos::new(0, 2),
279            ]),
280            FxHashSet::from_iter([
281                Pos::new(1, -1),
282                Pos::new(-1, -1),
283                Pos::new(-1, -3),
284                Pos::new(1, -3),
285            ]),
286        ];
287        let output = input.map(|p| p.corners().collect());
288        assert_eq!(expected, output, "\n input: {input:?}");
289    }
290
291    #[test]
292    fn neighbors() {
293        let input = [Pos::new(4, 5), Pos::new(-1, 3)];
294        let expected = [
295            FxHashSet::from_iter([
296                Pos::new(5, 5),
297                Pos::new(4, 6),
298                Pos::new(3, 5),
299                Pos::new(4, 4),
300                Pos::new(5, 6),
301                Pos::new(3, 6),
302                Pos::new(3, 4),
303                Pos::new(5, 4),
304            ]),
305            FxHashSet::from_iter([
306                Pos::new(0, 3),
307                Pos::new(-1, 4),
308                Pos::new(-2, 3),
309                Pos::new(-1, 2),
310                Pos::new(0, 4),
311                Pos::new(-2, 4),
312                Pos::new(-2, 2),
313                Pos::new(0, 2),
314            ]),
315        ];
316        let output = input.map(|p| p.neighbors().collect());
317        assert_eq!(expected, output, "\n input: {input:?}");
318    }
319
320    // ------------------------------------------------------------------------------------------------
321    // From
322
323    #[test]
324    fn pos_from_isize_tuple() {
325        let input: (isize, isize) = (1, 2);
326        let expected = Pos::new(1, 2);
327        let output = Pos::from(input);
328        assert_eq!(expected, output, "\n input: {input:?}");
329    }
330
331    #[test]
332    fn pos_from_usize_tuple() {
333        let input: (usize, usize) = (1, 2);
334        let expected = Pos::new(1, 2);
335        let output = Pos::from(input);
336        assert_eq!(expected, output, "\n input: {input:?}");
337    }
338
339    #[test]
340    fn usize_tuple_from_pos() {
341        let input = Pos::new(1, 2);
342        let expected = (1, 2);
343        let output = From::from(input);
344        assert_eq!(expected, output, "\n input: {input:?}");
345    }
346}