use derive_more::{Add, AddAssign, Display, Sub, SubAssign};
use crate::dir::Dir;
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Add, AddAssign, Sub, SubAssign, Display)]
#[display("{x},{y}")]
pub struct Pos {
pub x: isize,
pub y: isize,
}
impl Pos {
#[inline]
#[must_use]
pub const fn new(x: isize, y: isize) -> Self {
Self { x, y }
}
#[inline]
#[must_use]
pub const fn move_dir(&self, dir: Dir) -> Self {
match dir {
Dir::S => Self::new(self.x + 1, self.y),
Dir::E => Self::new(self.x, self.y + 1),
Dir::N => Self::new(self.x - 1, self.y),
Dir::W => Self::new(self.x, self.y - 1),
}
}
#[inline]
pub const fn move_dir_mut(&mut self, dir: Dir) {
match dir {
Dir::S => self.x += 1,
Dir::E => self.y += 1,
Dir::N => self.x -= 1,
Dir::W => self.y -= 1,
}
}
#[inline]
#[must_use]
pub const fn in_bounds(&self, bounds: (Self, Self)) -> bool {
bounds.0.x <= self.x && self.x <= bounds.1.x && bounds.0.y <= self.y && self.y <= bounds.1.y
}
#[inline]
#[must_use]
pub const fn manhattan_distance(&self, other: Self) -> usize {
self.x.abs_diff(other.x) + self.y.abs_diff(other.y)
}
#[inline]
pub fn adjacent(&self) -> impl Iterator<Item = Self> + use<> {
[
Self::new(self.x + 1, self.y),
Self::new(self.x, self.y + 1),
Self::new(self.x - 1, self.y),
Self::new(self.x, self.y - 1),
]
.into_iter()
}
#[inline]
pub fn corners(&self) -> impl Iterator<Item = Self> + use<> {
[
Self::new(self.x + 1, self.y + 1),
Self::new(self.x - 1, self.y + 1),
Self::new(self.x - 1, self.y - 1),
Self::new(self.x + 1, self.y - 1),
]
.into_iter()
}
#[inline]
pub fn neighbors(&self) -> impl Iterator<Item = Self> + use<> {
self.adjacent().chain(self.corners())
}
}
impl From<(isize, isize)> for Pos {
#[inline]
fn from(value: (isize, isize)) -> Self {
Self::new(value.0, value.1)
}
}
impl From<(usize, usize)> for Pos {
#[inline]
fn from(value: (usize, usize)) -> Self {
#[allow(clippy::cast_possible_wrap)]
Self::new(value.0 as isize, value.1 as isize)
}
}
impl From<Pos> for (usize, usize) {
#[inline]
fn from(val: Pos) -> Self {
#[allow(clippy::cast_sign_loss)]
(val.x as usize, val.y as usize)
}
}
pub trait PosGet<V> {
fn pos_get(&self, pos: Pos) -> Option<&V>;
fn pos_get_mut(&mut self, pos: Pos) -> Option<&mut V>;
}
#[cfg(test)]
mod tests {
use rustc_hash::FxHashSet;
use super::Pos;
use crate::dir::Dir::*;
#[test]
fn new() {
let input: (isize, isize) = (1, 2);
let expected = (1, 2);
let pos = Pos::new(input.0, input.1);
let output = (pos.x, pos.y);
assert_eq!(expected, output, "\n input: {input:?}");
}
#[test]
fn move_dir() {
let input = [S, E, N, W];
let expected = [
Pos::new(2, 2),
Pos::new(1, 3),
Pos::new(0, 2),
Pos::new(1, 1),
];
let pos = Pos::new(1, 2);
let output = input.map(|d| pos.move_dir(d));
assert_eq!(expected, output, "\n input: {input:?}");
}
#[test]
fn move_dir_mut() {
let input = [
(Pos::new(0, 1), S),
(Pos::new(1, 1), E),
(Pos::new(1, 2), N),
(Pos::new(2, 2), W),
];
let expected = [
(Pos::new(1, 1), S),
(Pos::new(1, 2), E),
(Pos::new(0, 2), N),
(Pos::new(2, 1), W),
];
let mut output = input;
for (pos, dir) in &mut output {
pos.move_dir_mut(*dir);
}
assert_eq!(expected, output, "\n input: {input:?}");
}
#[test]
fn in_bounds() {
let input = (
(Pos::new(1, 2), Pos::new(4, 7)),
[
Pos::new(2, 5),
Pos::new(1, 5),
Pos::new(4, 5),
Pos::new(2, 2),
Pos::new(2, 7),
Pos::new(0, 5),
Pos::new(5, 5),
Pos::new(2, 1),
Pos::new(2, 8),
],
);
let expected = [true, true, true, true, true, false, false, false, false];
let output = input.1.map(|p| p.in_bounds(input.0));
assert_eq!(expected, output, "\n input: {input:?}");
}
#[test]
fn manhattan_distance() {
let input = [
(Pos::new(1, 2), Pos::new(3, 4)),
(Pos::new(-4, 3), Pos::new(2, -1)),
(Pos::new(0, 0), Pos::new(-1, 1)),
(Pos::new(4, -3), Pos::new(-2, 2)),
];
let expected = [4, 10, 2, 11];
let output = input.map(|(p, q)| p.manhattan_distance(q));
assert_eq!(expected, output, "\n input: {input:?}");
}
#[test]
fn adjacent() {
let input = [
Pos::new(0, 0),
Pos::new(4, 5),
Pos::new(-1, 3),
Pos::new(0, -2),
];
let expected = [
FxHashSet::from_iter([
Pos::new(1, 0),
Pos::new(0, 1),
Pos::new(-1, 0),
Pos::new(0, -1),
]),
FxHashSet::from_iter([
Pos::new(5, 5),
Pos::new(4, 6),
Pos::new(3, 5),
Pos::new(4, 4),
]),
FxHashSet::from_iter([
Pos::new(0, 3),
Pos::new(-1, 4),
Pos::new(-2, 3),
Pos::new(-1, 2),
]),
FxHashSet::from_iter([
Pos::new(1, -2),
Pos::new(0, -1),
Pos::new(-1, -2),
Pos::new(0, -3),
]),
];
let output = input.map(|p| p.adjacent().collect());
assert_eq!(expected, output, "\n input: {input:?}");
}
#[test]
fn corners() {
let input = [
Pos::new(0, 0),
Pos::new(4, 5),
Pos::new(-1, 3),
Pos::new(0, -2),
];
let expected = [
FxHashSet::from_iter([
Pos::new(1, 1),
Pos::new(-1, 1),
Pos::new(-1, -1),
Pos::new(1, -1),
]),
FxHashSet::from_iter([
Pos::new(5, 6),
Pos::new(3, 6),
Pos::new(3, 4),
Pos::new(5, 4),
]),
FxHashSet::from_iter([
Pos::new(0, 4),
Pos::new(-2, 4),
Pos::new(-2, 2),
Pos::new(0, 2),
]),
FxHashSet::from_iter([
Pos::new(1, -1),
Pos::new(-1, -1),
Pos::new(-1, -3),
Pos::new(1, -3),
]),
];
let output = input.map(|p| p.corners().collect());
assert_eq!(expected, output, "\n input: {input:?}");
}
#[test]
fn neighbors() {
let input = [Pos::new(4, 5), Pos::new(-1, 3)];
let expected = [
FxHashSet::from_iter([
Pos::new(5, 5),
Pos::new(4, 6),
Pos::new(3, 5),
Pos::new(4, 4),
Pos::new(5, 6),
Pos::new(3, 6),
Pos::new(3, 4),
Pos::new(5, 4),
]),
FxHashSet::from_iter([
Pos::new(0, 3),
Pos::new(-1, 4),
Pos::new(-2, 3),
Pos::new(-1, 2),
Pos::new(0, 4),
Pos::new(-2, 4),
Pos::new(-2, 2),
Pos::new(0, 2),
]),
];
let output = input.map(|p| p.neighbors().collect());
assert_eq!(expected, output, "\n input: {input:?}");
}
#[test]
fn pos_from_isize_tuple() {
let input: (isize, isize) = (1, 2);
let expected = Pos::new(1, 2);
let output = Pos::from(input);
assert_eq!(expected, output, "\n input: {input:?}");
}
#[test]
fn pos_from_usize_tuple() {
let input: (usize, usize) = (1, 2);
let expected = Pos::new(1, 2);
let output = Pos::from(input);
assert_eq!(expected, output, "\n input: {input:?}");
}
#[test]
fn usize_tuple_from_pos() {
let input = Pos::new(1, 2);
let expected = (1, 2);
let output = From::from(input);
assert_eq!(expected, output, "\n input: {input:?}");
}
}