common/rust: add VecN type

This commit is contained in:
Xiretza 2022-12-19 19:26:52 +01:00
parent 6fb08930bc
commit ce17a061f3
4 changed files with 424 additions and 0 deletions

56
Cargo.lock generated
View file

@ -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"

View file

@ -7,3 +7,5 @@ edition = "2021"
[dependencies]
num-traits = "0.2.15"
strum = { version = "0.24.1", features = ["derive"] }
thiserror = "1.0.38"

View file

@ -2,6 +2,7 @@
mod section_range;
pub mod vec2;
pub mod vecn;
use std::{mem, path::Path};

365
common/rust/src/vecn.rs Normal file
View file

@ -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<const N: usize, T> {
coords: [T; N],
}
impl<T> VecN<2, T> {
#[must_use]
pub const fn new(x: T, y: T) -> Self {
Self { coords: [x, y] }
}
}
impl<T> VecN<3, T> {
#[must_use]
pub const fn new(x: T, y: T, z: T) -> Self {
Self { coords: [x, y, z] }
}
}
impl<const N: usize, T> VecN<N, T> {
#[must_use]
pub fn map<U>(self, f: impl FnMut(T) -> U) -> VecN<N, U> {
VecN {
coords: self.coords.map(f),
}
}
pub fn try_map<U, E>(self, mut f: impl FnMut(T) -> Result<U, E>) -> Result<VecN<N, U>, 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<f64>,
{
self.coords
.into_iter()
.map(|l| l.into().powi(2))
.sum::<f64>()
.sqrt()
}
#[must_use]
pub fn manhattan_len<U>(self) -> U
where
T: Abs<Output = U>,
U: Sum,
{
self.coords.into_iter().map(Abs::abs).sum()
}
}
impl<const N: usize, T> Add for VecN<N, T>
where
T: Add<Output = T>,
{
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<const N: usize, T> AddAssign for VecN<N, T>
where
T: AddAssign,
{
fn add_assign(&mut self, rhs: Self) {
for (l, r) in self.coords.iter_mut().zip(rhs.coords) {
*l += r;
}
}
}
impl<const N: usize, T> Sub for VecN<N, T>
where
T: Sub<Output = T>,
{
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<const N: usize, T> SubAssign for VecN<N, T>
where
T: SubAssign,
{
fn sub_assign(&mut self, rhs: Self) {
for (l, r) in self.coords.iter_mut().zip(rhs.coords) {
*l -= r;
}
}
}
impl<const N: usize, T, U> Mul<U> for VecN<N, T>
where
T: Mul<U, Output = T>,
U: Copy,
{
type Output = Self;
fn mul(self, n: U) -> Self::Output {
Self {
coords: self.coords.map(|c| c * n),
}
}
}
/*
impl<const N: usize, T> Mul<VecN<N, T>> for T
where
T: Mul<Output = T>,
{
type Output = VecN<N, T>;
fn mul(self, vec: VecN<N, T>) -> Self::Output {
VecN {
coords: vec.coords.map(|c| c * self),
}
}
}
*/
impl<const N: usize, T> From<[T; N]> for VecN<N, T> {
fn from(coords: [T; N]) -> Self {
Self { coords }
}
}
impl<const N: usize, T> From<VecN<N, T>> for [T; N] {
fn from(v: VecN<N, T>) -> Self {
v.coords
}
}
impl<T> From<Direction2> for VecN<2, T>
where
T: Zero + One + Neg<Output = T>,
{
/// 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<T> From<Direction3> for VecN<3, T>
where
T: Zero + One + Neg<Output = T> + 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<E> {
#[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<const N: usize, T> FromStr for VecN<N, T>
where
[T; N]: Default,
T: FromStr,
{
type Err = ParseVecError<T::Err>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<Self, Self::Err> {
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<Direction2> 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<Self, Self::Err> {
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(()),
}
}
}