diff --git a/2023/src/bin/day03.rs b/2023/src/bin/day03.rs index e8a29db..637615e 100644 --- a/2023/src/bin/day03.rs +++ b/2023/src/bin/day03.rs @@ -5,7 +5,7 @@ use std::ops::RangeInclusive; use aoc2023::{ boilerplate, common::*, - grid::{Position2D, PositionND}, + position::{Position2D, PositionND}, }; use itertools::Itertools; @@ -47,12 +47,12 @@ fn part1((grid, number_positions): &Parsed) -> usize { .flatten() .cloned() .filter_map(|(x, ys)| { - let start = Position2D::from([x, *ys.start()]); - let end = Position2D::from([x, *ys.end()]); + let start = PositionND([x, *ys.start()]); + let end = PositionND([x, *ys.end()]); start - .neighbors() + .neighbors_checked() .into_iter() - .chain(end.neighbors()) + .chain(end.neighbors_checked()) .any(|PositionND([x, y])| { !matches!(grid.get(x as usize).and_then(|ys| ys.get(y as usize)), Some(b'0'..=b'9' | b'.') | None) }) @@ -61,16 +61,16 @@ fn part1((grid, number_positions): &Parsed) -> usize { .sum() } -fn part_of(PositionND([x1, y]): Position2D, (x2, ys): &(usize, RangeInclusive)) -> bool { - x1 == *x2 as i64 && ys.contains(&(y as usize)) +fn part_of(PositionND([x1, y]): Position2D, (x2, ys): &(usize, RangeInclusive)) -> bool { + x1 == *x2 && ys.contains(&y) } fn part2((grid, number_positions): &Parsed) -> usize { grid.iter() .enumerate() - .flat_map(|(x, ys)| ys.iter().enumerate().filter_map(move |(y, &b)| (b == b'*').then_some(Position2D::from([x, y])))) + .flat_map(|(x, ys)| ys.iter().enumerate().filter_map(move |(y, &b)| (b == b'*').then_some(PositionND([x, y])))) .filter_map(|p| { - let neighbors = p.neighbors(); + let neighbors = p.neighbors_checked(); number_positions[((p.0[0] - 1) as usize)..=((p.0[0] + 1) as usize)] .iter() .flatten() diff --git a/2023/src/common.rs b/2023/src/common.rs index 283e048..b90d207 100644 --- a/2023/src/common.rs +++ b/2023/src/common.rs @@ -1,3 +1,5 @@ +use std::iter::Step; + pub fn read_file(day: usize) -> String { std::fs::read_to_string(std::env::var("AOC_INPUT").unwrap_or_else(|_| format!("inputs/day{day:0>2}"))).unwrap() } @@ -25,6 +27,21 @@ impl Splitting for &str { } } +pub trait Inc: Default + Copy + Step { + fn inc(self) -> Self; + fn dec(self) -> Self; +} + +impl Inc for T { + fn inc(self) -> Self { + T::forward(self, 1) + } + + fn dec(self) -> Self { + T::backward(self, 1) + } +} + #[cfg(debug_assertions)] pub fn parse_num(s: &str) -> T { s.parse().unwrap_or_else(|_| panic!("Invalid number {s}")) diff --git a/2023/src/grid.rs b/2023/src/grid.rs deleted file mode 100644 index 39eff06..0000000 --- a/2023/src/grid.rs +++ /dev/null @@ -1,159 +0,0 @@ -pub mod direction; -pub mod position; -pub use direction::*; -use fnv::FnvHashMap as HashMap; -use itertools::{join, Itertools, MinMaxResult}; -pub use position::*; -use std::{ - fmt::Display, - ops::{Index, IndexMut}, -}; - -#[allow(clippy::len_without_is_empty)] // I mainly have this for assertions in benchmarks -pub trait Grid: Index, Output = T> + IndexMut> { - fn get(&self, pos: &PositionND) -> Option<&T>; - - fn insert>>(&mut self, pos: Pos, element: T); - - fn len(&self) -> usize; -} - -#[derive(Debug, Clone, PartialEq)] -pub struct HashGrid { - pub fields: HashMap, T>, -} - -impl Index> for HashGrid { - type Output = T; - fn index(&self, index: PositionND) -> &Self::Output { - &self.fields[&index] - } -} - -impl IndexMut> for HashGrid { - fn index_mut(&mut self, index: PositionND) -> &mut Self::Output { - self.fields.get_mut(&index).expect("Key not in map") - } -} - -impl Grid for HashGrid { - fn get(&self, pos: &PositionND) -> Option<&T> { - self.fields.get(pos) - } - - fn insert>>(&mut self, pos: Pos, t: T) { - self.fields.insert(pos.into(), t); - } - - fn len(&self) -> usize { - self.fields.len() - } -} - -impl HashGrid { - pub fn from_bytes_2d T + Copy>(raw: &str, mut f: F) -> HashGrid { - raw.lines() - .enumerate() - .flat_map(move |(y, l)| l.bytes().enumerate().map(move |(x, c)| (PositionND([x as i64, y as i64]), f(c)))) - .collect() - } -} - -impl std::iter::FromIterator<(PositionND, T)> for HashGrid { - fn from_iter, T)>>(iter: I) -> Self { - HashGrid { fields: iter.into_iter().collect() } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct VecGrid { - pub fields: Vec>, -} - -impl Grid for VecGrid { - fn get(&self, pos: &PositionND<2>) -> Option<&T> { - self.fields.get(pos.0[0] as usize)?.get(pos.0[1] as usize) - } - - fn insert>>(&mut self, pos: Pos, element: T) { - let PositionND([x, y]) = pos.into(); - self.fields[x as usize][y as usize] = element; - } - - fn len(&self) -> usize { - self.fields.len() - } -} - -impl Index for VecGrid { - type Output = T; - fn index(&self, index: Position2D) -> &Self::Output { - &self.fields[index.0[0] as usize][index.0[1] as usize] - } -} - -impl IndexMut for VecGrid { - fn index_mut(&mut self, index: Position2D) -> &mut Self::Output { - &mut self.fields[index.0[0] as usize][index.0[1] as usize] - } -} - -impl VecGrid { - pub fn from_bytes_2d T + Copy>(raw: &str, f: F) -> VecGrid { - VecGrid { fields: raw.lines().map(|l| l.bytes().map(f).collect()).collect() } - } -} - -pub struct Boundaries { - pub x_min: i64, - pub x_max: i64, - pub y_min: i64, - pub y_max: i64, -} - -pub fn get_boundaries(input: &[&PositionND<2>]) -> Boundaries { - let (x_min, x_max) = match input.iter().map(|p| p.0[0]).minmax() { - MinMaxResult::NoElements => (0, 0), - MinMaxResult::MinMax(min, max) => (min, max), - MinMaxResult::OneElement(x) => (x, x), - }; - let (y_min, y_max) = match input.iter().map(|p| p.0[1]).minmax() { - MinMaxResult::NoElements => (0, 0), - MinMaxResult::MinMax(min, max) => (min, max), - MinMaxResult::OneElement(x) => (x, x), - }; - Boundaries { x_min, x_max, y_min, y_max } -} - -pub fn draw_ascii(coordinates: &HashMap, T>) -> String { - let b = get_boundaries(&coordinates.keys().collect::>()); - join( - (b.x_min..=b.x_max).map(|x| { - (b.y_min..=b.y_max).map(|y| coordinates.get(&PositionND([x, y])).unwrap_or(&T::default()).to_string()).collect::() - }), - "\n", - ) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_add() { - assert_eq!(PositionND([0, 2]) + PositionND([-1, 0]), [-1, 2].into()); - assert_eq!(PositionND([0, -1]) + PositionND::from(Direction::Up), [0, 0].into()); - } - - #[test] - fn test_sub() { - assert_eq!(PositionND([0, 2]) - PositionND([-1, 0]), [1, 2].into()); - assert_eq!(PositionND([0, -1]) - PositionND([0, -1]), [0, 0].into()); - } - - #[test] - fn test_mul() { - assert_eq!(PositionND([0, 2]) * 5, [0, 10].into()); - assert_eq!(PositionND([0, -1]) * -2, [0, 2].into()); - } -} diff --git a/2023/src/lib.rs b/2023/src/lib.rs index 63928ce..07d2ec0 100644 --- a/2023/src/lib.rs +++ b/2023/src/lib.rs @@ -1,5 +1,5 @@ #![allow(incomplete_features)] -#![feature(test, generic_const_exprs)] +#![feature(test, generic_const_exprs, step_trait)] pub mod common; -pub mod grid; +pub mod position; pub mod teststuff; diff --git a/2023/src/grid/position.rs b/2023/src/position.rs similarity index 59% rename from 2023/src/grid/position.rs rename to 2023/src/position.rs index a740298..07774a7 100644 --- a/2023/src/grid/position.rs +++ b/2023/src/position.rs @@ -1,39 +1,28 @@ extern crate test; -use super::direction::*; use std::{ - convert::TryInto, hash::Hash, - ops::{Add, Mul, Sub}, + iter::Step, + ops::{Add, AddAssign}, }; +use crate::common::Inc; + #[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)] -pub struct PositionND(pub [i64; DIMS]); +pub struct PositionND(pub [I; DIMS]); -pub type Position2D = PositionND<2>; - -impl From<[I; D]> for PositionND -where I: TryInto + Copy -{ - fn from(s: [I; D]) -> Self { - let mut points = [0; D]; - for i in 0..D { - points[i] = s[i].try_into().unwrap_or_else(|_| panic!("number did not fit in target type")) - } - Self(points) - } -} +pub type Position2D = PositionND; pub const fn num_neighbors(d: usize) -> usize { 3usize.pow(d as u32) - 1 } -impl PositionND { - pub const fn zero() -> Self { - PositionND([0; DIMS]) +impl + AddAssign, const DIMS: usize> PositionND { + pub fn zero() -> Self { + PositionND([I::default(); DIMS]) } - pub fn from_padded(slice: &[i64]) -> PositionND { - let mut points = [0; DIMS]; + pub fn from_padded(slice: &[I]) -> PositionND { + let mut points = [I::default(); DIMS]; #[allow(clippy::manual_memcpy)] for i in 0..(DIMS.min(slice.len())) { points[i] = slice[i]; @@ -41,36 +30,63 @@ impl PositionND { PositionND(points) } - pub fn neighbors(&self) -> [PositionND; num_neighbors(DIMS)] - where [PositionND; num_neighbors(DIMS) + 1]: Sized { - let ns = neighbor_vectors::(); + pub fn neighbors(&self) -> [PositionND; num_neighbors(DIMS)] + where [PositionND; num_neighbors(DIMS) + 1]: Sized { + let ns = neighbor_vectors::(); let mut out = [*self; num_neighbors(DIMS)]; - for (out, dir) in out.iter_mut().zip(IntoIterator::into_iter(ns).filter(|n| n != &[0; DIMS])) { - *out = *out + PositionND::from(dir); + for (out, dir) in out.iter_mut().zip(IntoIterator::into_iter(ns).filter(|n| n != &[I::default(); DIMS])) { + *out = *out + PositionND(dir); } out } + + pub fn neighbors_checked(&self) -> Vec> + where [PositionND; num_neighbors(DIMS) + 1]: Sized { + neighbor_vectors::() + .into_iter() + .filter_map(|n| { + let mut out = *self; + for i in 0..DIMS { + if n[i] > I::default() { + out.0[i] = Step::forward_checked(out.0[i], 1)?; + } else { + out.0[i] = Step::backward_checked(out.0[i], 1)?; + } + } + Some(out) + }) + .collect() + } } -impl PositionND<2> { - pub fn neighbors_no_diagonals_only_positive(&self) -> [PositionND<2>; 2] { - let PositionND::<2>([x, y]) = *self; - [[x + 1, y].into(), [x, y + 1].into()] - } +impl Add> for PositionND +where I: AddAssign + Copy +{ + type Output = PositionND; - pub fn neighbors_no_diagonals(&self) -> [PositionND<2>; 4] { - let PositionND::<2>([x, y]) = *self; - [[x + 1, y].into(), [x, y + 1].into(), [x - 1, y].into(), [x, y - 1].into()] + fn add(mut self, rhs: PositionND) -> Self::Output { + for (x, y) in self.0.iter_mut().zip(rhs.0) { + *x += y; + } + self + } +} + +impl PositionND { + pub fn neighbors_no_diagonals(&self) -> [PositionND; 4] { + let PositionND([x, y]) = *self; + [PositionND([x.inc(), y]), PositionND([x, y.inc()]), PositionND([x.dec(), y]), PositionND([x, y.dec()])] } } #[macro_export] macro_rules! dim { - ($d: expr) => {{ - let mut out = [[0; D]; num_neighbors(D) + 1]; + ($d: expr, $i:ty) => {{ + let zero: $i = Default::default(); + let mut out = [[zero; D]; num_neighbors(D) + 1]; let mut i = 0; - for offset in -1..=1 { - for inner in neighbor_vectors::<$d>() { + for offset in zero.dec()..=zero.inc() { + for inner in neighbor_vectors::<$i, $d>() { out[i][0] = offset; let mut j = 1; for e in inner { @@ -84,7 +100,7 @@ macro_rules! dim { }}; } -fn neighbor_vectors() -> [[i64; D]; num_neighbors(D) + 1] +fn neighbor_vectors() -> [[I; D]; num_neighbors(D) + 1] where { // I would love to just call neighbor_vectors::(), but it seems to be impossible to get the @@ -92,67 +108,24 @@ where match D { 0 => unreachable!(), 1 => { - let mut out = [[0; D]; num_neighbors(D) + 1]; - out[0] = [-1; D]; - out[1] = [0; D]; - out[2] = [1; D]; + let zero = I::default(); + let mut out = [[zero; D]; num_neighbors(D) + 1]; + out[0] = [zero.dec(); D]; + out[1] = [zero; D]; + out[2] = [zero.inc(); D]; out } - 2 => dim!(1), - 3 => dim!(2), - 4 => dim!(3), - 5 => dim!(4), - 6 => dim!(5), - 7 => dim!(6), + 2 => dim!(1, I), + 3 => dim!(2, I), + 4 => dim!(3, I), + 5 => dim!(4, I), + 6 => dim!(5, I), + 7 => dim!(6, I), // Adding more causes a stackoverflow. How curious. _ => unimplemented!(), } } -impl Mul for PositionND { - type Output = PositionND; - - fn mul(mut self, rhs: i64) -> Self::Output { - for p in self.0.iter_mut() { - *p *= rhs; - } - self - } -} - -impl Add> for PositionND { - type Output = PositionND; - - fn add(mut self, rhs: PositionND) -> Self::Output { - for (x, y) in self.0.iter_mut().zip(rhs.0) { - *x += y; - } - self - } -} - -impl Sub> for PositionND { - type Output = PositionND; - - fn sub(mut self, rhs: PositionND) -> Self::Output { - for (x, y) in self.0.iter_mut().zip(rhs.0) { - *x -= y; - } - self - } -} - -impl From for PositionND<2> { - fn from(d: Direction) -> Self { - match d { - Direction::Up => PositionND::from([0, 1]), - Direction::Right => PositionND::from([1, 0]), - Direction::Left => PositionND::from([-1, 0]), - Direction::Down => PositionND::from([0, -1]), - } - } -} - #[cfg(test)] mod tests { use super::*;