use std::{num::ParseIntError, str::FromStr}; use itertools::Itertools; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Point { pub row: usize, pub col: usize, } impl FromStr for Point { type Err = ParseIntError; fn from_str(s: &str) -> Result { let (row, col) = s.split_once(',').unwrap(); Ok(Self { row: row.parse()?, col: col.parse()?, }) } } impl Point { pub fn rectangular_area(&self, r: &Self) -> usize { let height = self.row.abs_diff(r.row) + 1; let width = self.col.abs_diff(r.col) + 1; width * height } } #[derive(Debug, Clone)] pub struct RightAngledShape { corners: Vec, } impl RightAngledShape { pub fn new(corners: &[Point]) -> Self { Self { corners: corners.to_vec(), } } pub fn contains_rect(&self, rect: (Point, Point)) -> bool { !(Self::rect_strict_contains_line( rect, (self.corners[0], self.corners[self.corners.len() - 1]), ) || self .corners .windows(2) .any(|window| Self::rect_strict_contains_line(rect, (window[0], window[1])))) } fn rect_strict_contains_line(rect: (Point, Point), line: (Point, Point)) -> bool { let left_col = rect.0.col.min(rect.1.col); let right_col = rect.0.col.max(rect.1.col); let top_row = rect.0.row.min(rect.1.row); let bottom_row = rect.0.row.max(rect.1.row); if line.0.row == line.1.row { //horizontal line let row_cond = line.0.row >= bottom_row || line.0.row <= top_row; let left_cond = line.0.col <= left_col && line.1.col <= left_col; let right_cond = line.0.col >= right_col && line.1.col >= right_col; !row_cond && !left_cond && !right_cond } else { //vertical line let col_cond = line.0.col >= right_col || line.0.col <= left_col; let above_cond = line.0.row <= top_row && line.1.row <= top_row; let below_cond = line.0.row >= bottom_row && line.1.row >= bottom_row; !col_cond && !above_cond && !below_cond } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Point3D { pub x: usize, pub y: usize, pub z: usize, } impl Point3D { pub fn euclidean_distance(&self, other: &Self) -> f64 { (((self.x as isize - other.x as isize).abs().pow(2) + (self.y as isize - other.y as isize).abs().pow(2) + (self.z as isize - other.z as isize).abs().pow(2)) as f64) .sqrt() } } pub fn all_coords(width: usize, height: usize) -> impl Iterator { (0..height) .cartesian_product(0..width) .map(|(row, col)| Point { row, col }) } /// North, East, South, West, Northeast, Southeast, Southwest, Northwest /// Depends on `coords` being within bounds pub fn adjacent_including_diagonals( grid: &[Vec], coords: Point, ) -> [Option; 8] { [ adjacent_cardinal(grid, coords), adjacent_ordinal(grid, coords), ] .concat() .try_into() .unwrap() } //could make these use .get() for safe out-of-bounds reads, i suppose ///Never Eat Shredded Wheat fn adjacent_cardinal(grid: &[Vec], coords: Point) -> [Option; 4] { let mut retval = [const { None }; 4]; let (row, col) = (coords.row, coords.col); if row > 0 { retval[0] = Some(grid[row - 1][col]) //N } if col < grid[0].len() - 1 { retval[1] = Some(grid[row][col + 1]) //E } if row < grid.len() - 1 { retval[2] = Some(grid[row + 1][col]) //S } if col > 0 { retval[3] = Some(grid[row][col - 1]) //N } retval } /// Never Eat Shredded Wheat, rotated 45 degrees clockwise /// AKA northeast, southeast, southwest, northwest fn adjacent_ordinal(grid: &[Vec], coords: Point) -> [Option; 4] { let mut retval = [const { None }; 4]; let (row, col) = (coords.row, coords.col); if row > 0 && col < grid[0].len() - 1 { retval[0] = Some(grid[row - 1][col + 1]) //NE } if row < grid.len() - 1 && col < grid[0].len() - 1 { retval[1] = Some(grid[row + 1][col + 1]) //SE } if row < grid.len() - 1 && col > 0 { retval[2] = Some(grid[row + 1][col - 1]) //SW } if row > 0 && col > 0 { retval[3] = Some(grid[row - 1][col - 1]) //NW } retval } pub fn transpose, T: Copy>(input: &[S]) -> Vec> { (0..input[0].as_ref().len()) .map(|col| input.iter().map(|row| row.as_ref()[col]).collect()) .collect() }