From faa452149ad674f710560666231250652efd0123 Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Thu, 4 Dec 2025 15:49:37 -0800 Subject: [PATCH] add neighbour and cardinal iterators to grid --- src/day4.rs | 5 +- utils/grid/lib.rs | 148 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 127 insertions(+), 26 deletions(-) diff --git a/src/day4.rs b/src/day4.rs index 16315ac..db77825 100644 --- a/src/day4.rs +++ b/src/day4.rs @@ -10,11 +10,10 @@ fn parse(input: &str) -> Grid { fn part1(input: &Grid) -> u64 { (0..input.height() * input.width()) .filter(|i| *input.get(&input.coord(*i as i64).unwrap()).unwrap() == b'@') - .map(|i| input.neighbours_count(input.coord(i as i64).unwrap(), |c| *c == b'@')) + .map(|i| input.neighbours_count(&input.coord(i as i64).unwrap(), |c| *c == b'@')) .filter(|n| *n < 4) .count() as u64 } - #[aoc(day4, part2)] fn part2(input: &Grid) -> u64 { let mut grid = input.clone(); @@ -25,7 +24,7 @@ fn part2(input: &Grid) -> u64 { for i in 0..grid.width() * grid.height() { let pos = grid.coord(i as i64).unwrap(); if grid.get(&pos).is_some_and(|c| *c == b'@') - && grid.neighbours_count(pos, |c| *c == b'@') < 4 + && grid.neighbours_count(&pos, |c| *c == b'@') < 4 { // remove the roll grid.set(&pos, b'.'); diff --git a/utils/grid/lib.rs b/utils/grid/lib.rs index ff5f605..936e2b1 100644 --- a/utils/grid/lib.rs +++ b/utils/grid/lib.rs @@ -7,6 +7,21 @@ use std::{ str::FromStr, }; +/// NW, N, NE, W, E, SW, S, SE +const ADJACENT_OFFSETS: [&(i64, i64); 8] = [ + &(-1, -1), + &(0, -1), + &(1, -1), + &(-1, 0), + &(1, 0), + &(-1, 1), + &(0, 1), + &(1, 1), +]; + +/// NESW +const CARDINAL_OFFSETS: [&(i64, i64); 4] = [&(0, -1), &(-1, 0), &(1, 0), &(0, 1)]; + #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Coord2d { pub x: i64, @@ -29,7 +44,10 @@ impl Sub for &Coord2d { } } -impl Add for &Coord2d { +impl Add for &Coord2d +where + T: Copy, +{ type Output = Coord2d; fn add(self, rhs: T) -> Self::Output { Coord2d { @@ -39,7 +57,7 @@ impl Add for &Coord2d { } } -impl Add<&T> for Coord2d { +impl Add<&T> for Coord2d { type Output = Coord2d; fn add(self, rhs: &T) -> Self::Output { Coord2d { @@ -150,7 +168,7 @@ impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for CoordIter<'a, T> { fn next(&mut self) -> Option { if self.pos < self.grid.data.len() { self.pos += 1; - self.grid.coord(self.pos as i64 - 1).into() + self.grid.coord(self.pos as i64 - 1) } else { None } @@ -163,6 +181,7 @@ pub struct ItemIter<'a, T> { grid: &'a Grid, } +#[derive(Debug, PartialEq, Eq)] pub struct Item<'a, T> { pub pos: Coord2d, pub value: &'a T, @@ -216,6 +235,31 @@ impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for GridColIter<'a, T> { } } +#[derive(Debug)] +pub struct OffsetsIter<'a, T> { + grid: &'a Grid, + origin: Coord2d, + cur: usize, + offsets: &'a [&'a (i64, i64)], +} + +impl<'a, T: Clone + Eq + PartialEq + Debug> Iterator for OffsetsIter<'a, T> { + type Item = Item<'a, T>; + fn next(&mut self) -> Option { + while self.cur < self.offsets.len() { + let pos = self.origin + self.offsets[self.cur]; + self.cur += 1; + if let Some(value) = self.grid.get(&pos) { + return Some(Item { pos, value }); + } + } + None + } + fn size_hint(&self) -> (usize, Option) { + (0, Some(self.offsets.len() - self.cur)) + } +} + #[derive(Clone, Eq, PartialEq, Debug)] pub struct Grid { pub data: Vec, @@ -266,7 +310,7 @@ impl Grid { if c.y() < 0 || c.y() as usize >= self.height() { return false; } - return true; + true } fn valid_pos(&self, c: &C) -> Option { if c.x() < 0 || c.x() >= self.width { @@ -331,11 +375,7 @@ impl Grid { } pub fn col(&self, x: i64) -> Option> { - if let Some(iter) = self.col_iter(x) { - Some(iter.collect()) - } else { - None - } + self.col_iter(x).map(|iter| iter.collect()) } pub fn col_iter<'a>(&'a self, x: i64) -> Option> { @@ -390,27 +430,37 @@ impl Grid { } /// Return the count of neighbours (8 directions) matching predicate p - pub fn neighbours_count(&self, c: C, mut p: P) -> usize + pub fn neighbours_count(&self, c: &C, mut p: P) -> usize where P: FnMut(&T) -> bool, { - const DIRECTIONS: [(i64, i64); 8] = [ - (-1, -1), - (0, -1), - (1, -1), - (-1, 0), - (1, 0), - (1, 1), - (0, 1), - (-1, 1), - ]; - DIRECTIONS + ADJACENT_OFFSETS .iter() - .map(|d| (c.x() + d.0, c.y() + d.1)) - .filter(|c| self.get(c).is_some_and(|x| p(x))) + .map(|d| c.to_coord() + *d) + .filter(|c| self.get(c).is_some_and(&mut p)) .count() } + /// Return an iterator over the 8 neighbours of c. The iterator skips neighbouring positions outside of the grid. + pub fn neighbours_iter(&self, c: &C) -> OffsetsIter { + OffsetsIter { + grid: self, + origin: c.to_coord(), + cur: 0, + offsets: &ADJACENT_OFFSETS, + } + } + + /// Return an iterator over the 4 cardinal neighbours of c. The iterator skips neighbouring positions outside of the grid. + pub fn cardinal_iter(&self, c: &C) -> OffsetsIter { + OffsetsIter { + grid: self, + origin: c.to_coord(), + cur: 0, + offsets: &CARDINAL_OFFSETS, + } + } + // fn window_compare_impl(&self, needle: &[T]) -> Vec<(i64, i64)> { // if (self.width as usize) < needle.len() { // return Vec::new(); @@ -582,6 +632,58 @@ FBCG"; ) } + #[test] + fn neighbours_iter() { + let grid = unchecked_load(); + assert_eq!( + grid.neighbours_iter(&(0, 1)).collect::>(), + [ + Item { + pos: Coord2d { x: 0, y: 0 }, + value: &b'A' + }, + Item { + pos: Coord2d { x: 1, y: 0 }, + value: &b'B' + }, + Item { + pos: Coord2d { x: 1, y: 1 }, + value: &b'F' + }, + Item { + pos: Coord2d { x: 0, y: 2 }, + value: &b'I' + }, + Item { + pos: Coord2d { x: 1, y: 2 }, + value: &b'J' + }, + ] + ) + } + + #[test] + fn cardinal_iter() { + let grid = unchecked_load(); + assert_eq!( + grid.cardinal_iter(&(0, 1)).collect::>(), + [ + Item { + pos: Coord2d { x: 0, y: 0 }, + value: &b'A' + }, + Item { + pos: Coord2d { x: 1, y: 1 }, + value: &b'F' + }, + Item { + pos: Coord2d { x: 0, y: 2 }, + value: &b'I' + } + ] + ) + } + // #[test] // fn window_compare() { // let grid = unchecked_load();