diff --git a/Cargo.lock b/Cargo.lock index a9a0cc7..9adcd0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,8 @@ name = "aoc" version = "0.1.0" dependencies = [ "num-traits", + "strum", + "thiserror", ] [[package]] @@ -213,6 +215,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -611,6 +619,12 @@ dependencies = [ "aoc", ] +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + [[package]] name = "ryu" version = "1.0.11" @@ -670,6 +684,28 @@ dependencies = [ "serde", ] +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "1.0.105" @@ -690,6 +726,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tinytemplate" version = "1.2.1" diff --git a/common/rust/Cargo.toml b/common/rust/Cargo.toml index 00a20b8..ebe2750 100644 --- a/common/rust/Cargo.toml +++ b/common/rust/Cargo.toml @@ -7,3 +7,5 @@ edition = "2021" [dependencies] num-traits = "0.2.15" +strum = { version = "0.24.1", features = ["derive"] } +thiserror = "1.0.38" diff --git a/common/rust/src/lib.rs b/common/rust/src/lib.rs index c79c26f..aa9633a 100644 --- a/common/rust/src/lib.rs +++ b/common/rust/src/lib.rs @@ -2,6 +2,7 @@ mod section_range; pub mod vec2; +pub mod vecn; use std::{mem, path::Path}; diff --git a/common/rust/src/vecn.rs b/common/rust/src/vecn.rs new file mode 100644 index 0000000..2219986 --- /dev/null +++ b/common/rust/src/vecn.rs @@ -0,0 +1,365 @@ +use std::{ + array, + iter::Sum, + ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign}, + str::FromStr, +}; + +use num_traits::{one, zero, One, Zero}; +use strum::EnumIter; +use thiserror::Error; + +pub trait Abs { + type Output; + fn abs(self) -> Self::Output; +} + +macro_rules! impl_abs_signed { + ($($t:ty)*) => { + $(impl Abs for $t { + type Output = $t; + fn abs(self) -> Self { + self.abs() + } + })* + }; +} +impl_abs_signed!(i8 i16 i32 i64 i128 f32 f64); + +macro_rules! impl_abs_noop { + ($($t:ty)*) => { + $(impl Abs for $t { + type Output = $t; + fn abs(self) -> Self { + self + } + })* + }; +} +impl_abs_noop!(u8 u16 u32 u64 u128); + +/// A vector in `N`-space. +/// +/// The coordinate system is canonically left-handed: +/// +/// Axis | + | - +/// -----|---------|------ +/// x | right | left +/// y | up | down +/// z | forward | back +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct VecN { + coords: [T; N], +} + +impl VecN<2, T> { + #[must_use] + pub const fn new(x: T, y: T) -> Self { + Self { coords: [x, y] } + } +} + +impl VecN<3, T> { + #[must_use] + pub const fn new(x: T, y: T, z: T) -> Self { + Self { coords: [x, y, z] } + } +} + +impl VecN { + #[must_use] + pub fn map(self, f: impl FnMut(T) -> U) -> VecN { + VecN { + coords: self.coords.map(f), + } + } + + pub fn try_map(self, mut f: impl FnMut(T) -> Result) -> Result, E> + where + [U; N]: Default, + { + let mut coords: [U; N] = Default::default(); + for (coord, x) in coords.iter_mut().zip(self.coords) { + *coord = f(x)?; + } + Ok(VecN { coords }) + } + + #[must_use] + pub fn len(self) -> f64 + where + T: Into, + { + self.coords + .into_iter() + .map(|l| l.into().powi(2)) + .sum::() + .sqrt() + } + + #[must_use] + pub fn manhattan_len(self) -> U + where + T: Abs, + U: Sum, + { + self.coords.into_iter().map(Abs::abs).sum() + } +} + +impl Add for VecN +where + T: Add, +{ + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + let mut coords = self.coords.into_iter().zip(rhs.coords).map(|(l, r)| l + r); + Self { + coords: array::from_fn(|_| coords.next().unwrap()), + } + } +} + +impl AddAssign for VecN +where + T: AddAssign, +{ + fn add_assign(&mut self, rhs: Self) { + for (l, r) in self.coords.iter_mut().zip(rhs.coords) { + *l += r; + } + } +} + +impl Sub for VecN +where + T: Sub, +{ + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + let mut coords = self.coords.into_iter().zip(rhs.coords).map(|(l, r)| l - r); + Self { + coords: array::from_fn(|_| coords.next().unwrap()), + } + } +} + +impl SubAssign for VecN +where + T: SubAssign, +{ + fn sub_assign(&mut self, rhs: Self) { + for (l, r) in self.coords.iter_mut().zip(rhs.coords) { + *l -= r; + } + } +} + +impl Mul for VecN +where + T: Mul, + U: Copy, +{ + type Output = Self; + + fn mul(self, n: U) -> Self::Output { + Self { + coords: self.coords.map(|c| c * n), + } + } +} + +/* +impl Mul> for T +where + T: Mul, +{ + type Output = VecN; + + fn mul(self, vec: VecN) -> Self::Output { + VecN { + coords: vec.coords.map(|c| c * self), + } + } +} +*/ + +impl From<[T; N]> for VecN { + fn from(coords: [T; N]) -> Self { + Self { coords } + } +} + +impl From> for [T; N] { + fn from(v: VecN) -> Self { + v.coords + } +} + +impl From for VecN<2, T> +where + T: Zero + One + Neg, +{ + /// Creates a unit vector in the given direction. + fn from(dir: Direction2) -> Self { + let z = zero(); + let o = one(); + + match dir { + Direction2::Right => Self::new(o, z), + Direction2::Left => Self::new(-o, z), + Direction2::Up => Self::new(z, o), + Direction2::Down => Self::new(z, -o), + } + } +} + +impl From for VecN<3, T> +where + T: Zero + One + Neg + Copy, +{ + /// Creates a unit vector in the given direction. + fn from(dir: Direction3) -> Self { + let z = zero(); + let o = one(); + + match dir { + Direction3::Right => Self::new(o, z, z), + Direction3::Left => Self::new(-o, z, z), + Direction3::Up => Self::new(z, o, z), + Direction3::Down => Self::new(z, -o, z), + Direction3::Forward => Self::new(z, z, o), + Direction3::Back => Self::new(z, z, -o), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)] +pub enum ParseVecError { + #[error("failed to parse axis {axis}")] + AxisParseError { + axis: usize, + #[source] + source: E, + }, + #[error("not enough dimensions specified, expected {expected}")] + NotEnoughDimensions { expected: usize }, + #[error("too many dimensions specified, expected {expected}")] + TooManyDimensions { expected: usize }, +} + +impl FromStr for VecN +where + [T; N]: Default, + T: FromStr, +{ + type Err = ParseVecError; + + fn from_str(s: &str) -> Result { + let mut parts = s.split(','); + + let mut coords: [T; N] = Default::default(); + for (i, axis) in coords.iter_mut().enumerate() { + *axis = parts + .next() + .ok_or(ParseVecError::NotEnoughDimensions { expected: N })? + .parse() + .map_err(|e| ParseVecError::AxisParseError { axis: i, source: e })?; + } + + if parts.next().is_some() { + return Err(ParseVecError::TooManyDimensions { expected: N }); + } + + Ok(Self { coords }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, EnumIter)] +pub enum Direction2 { + Right, + Left, + Up, + Down, +} + +impl Neg for Direction2 { + type Output = Self; + + fn neg(self) -> Self::Output { + match self { + Direction2::Right => Direction2::Left, + Direction2::Left => Direction2::Right, + Direction2::Up => Direction2::Down, + Direction2::Down => Direction2::Up, + } + } +} + +impl FromStr for Direction2 { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "R" => Ok(Direction2::Right), + "L" => Ok(Direction2::Left), + "U" => Ok(Direction2::Up), + "D" => Ok(Direction2::Down), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, EnumIter)] +pub enum Direction3 { + Right = 0, + Left = 1, + Up = 2, + Down = 4, + Forward = 5, + Back = 6, +} + +impl From for Direction3 { + fn from(dir: Direction2) -> Self { + match dir { + Direction2::Right => Direction3::Right, + Direction2::Left => Direction3::Left, + Direction2::Up => Direction3::Up, + Direction2::Down => Direction3::Down, + } + } +} + +impl Neg for Direction3 { + type Output = Self; + + fn neg(self) -> Self::Output { + match self { + Direction3::Right => Direction3::Left, + Direction3::Left => Direction3::Right, + Direction3::Up => Direction3::Down, + Direction3::Down => Direction3::Up, + Direction3::Forward => Direction3::Back, + Direction3::Back => Direction3::Forward, + } + } +} + +impl FromStr for Direction3 { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "R" => Ok(Direction3::Right), + "L" => Ok(Direction3::Left), + "U" => Ok(Direction3::Up), + "D" => Ok(Direction3::Down), + "F" => Ok(Direction3::Forward), + "B" => Ok(Direction3::Back), + _ => Err(()), + } + } +}