1use std::{num::ParseIntError, str::FromStr};
2
3use itertools::Itertools;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6pub struct Point {
7 pub row: usize,
8 pub col: usize,
9}
10
11impl FromStr for Point {
12 type Err = ParseIntError;
13 fn from_str(s: &str) -> Result<Self, Self::Err> {
14 let (row, col) = s.split_once(',').unwrap();
15 Ok(Self {
16 row: row.parse()?,
17 col: col.parse()?,
18 })
19 }
20}
21
22impl Point {
23 pub fn rectangular_area(&self, r: &Self) -> usize {
24 let height = self.row.abs_diff(r.row) + 1;
25 let width = self.col.abs_diff(r.col) + 1;
26 width * height
27 }
28}
29
30#[derive(Debug, Clone)]
31pub struct RightAngledShape {
32 corners: Vec<Point>,
33}
34
35impl RightAngledShape {
36 pub fn new(corners: &[Point]) -> Self {
37 Self {
38 corners: corners.to_vec(),
39 }
40 }
41
42 pub fn contains_rect(&self, rect: (Point, Point)) -> bool {
43 !(Self::rect_strict_contains_line(
44 rect,
45 (self.corners[0], self.corners[self.corners.len() - 1]),
46 ) || self
47 .corners
48 .windows(2)
49 .any(|window| Self::rect_strict_contains_line(rect, (window[0], window[1]))))
50 }
51
52 fn rect_strict_contains_line(rect: (Point, Point), line: (Point, Point)) -> bool {
53 let left_col = rect.0.col.min(rect.1.col);
54 let right_col = rect.0.col.max(rect.1.col);
55 let top_row = rect.0.row.min(rect.1.row);
56 let bottom_row = rect.0.row.max(rect.1.row);
57
58 if line.0.row == line.1.row {
59 //horizontal line
60 let row_cond = line.0.row >= bottom_row || line.0.row <= top_row;
61 let left_cond = line.0.col <= left_col && line.1.col <= left_col;
62 let right_cond = line.0.col >= right_col && line.1.col >= right_col;
63
64 !row_cond && !left_cond && !right_cond
65 } else {
66 //vertical line
67 let col_cond = line.0.col >= right_col || line.0.col <= left_col;
68 let above_cond = line.0.row <= top_row && line.1.row <= top_row;
69 let below_cond = line.0.row >= bottom_row && line.1.row >= bottom_row;
70
71 !col_cond && !above_cond && !below_cond
72 }
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
77pub struct Point3D {
78 pub x: usize,
79 pub y: usize,
80 pub z: usize,
81}
82
83impl Point3D {
84 pub fn euclidean_distance(&self, other: &Self) -> f64 {
85 (((self.x as isize - other.x as isize).abs().pow(2)
86 + (self.y as isize - other.y as isize).abs().pow(2)
87 + (self.z as isize - other.z as isize).abs().pow(2)) as f64)
88 .sqrt()
89 }
90}
91
92pub fn all_coords(width: usize, height: usize) -> impl Iterator<Item = Point> {
93 (0..height)
94 .cartesian_product(0..width)
95 .map(|(row, col)| Point { row, col })
96}
97
98/// North, East, South, West, Northeast, Southeast, Southwest, Northwest
99/// Depends on `coords` being within bounds
100pub fn adjacent_including_diagonals<T: Copy + std::fmt::Debug>(
101 grid: &[Vec<T>],
102 coords: Point,
103) -> [Option<T>; 8] {
104 [
105 adjacent_cardinal(grid, coords),
106 adjacent_ordinal(grid, coords),
107 ]
108 .concat()
109 .try_into()
110 .unwrap()
111}
112
113//could make these use .get() for safe out-of-bounds reads, i suppose
114///Never Eat Shredded Wheat
115fn adjacent_cardinal<T: Copy>(grid: &[Vec<T>], coords: Point) -> [Option<T>; 4] {
116 let mut retval = [const { None }; 4];
117 let (row, col) = (coords.row, coords.col);
118
119 if row > 0 {
120 retval[0] = Some(grid[row - 1][col]) //N
121 }
122 if col < grid[0].len() - 1 {
123 retval[1] = Some(grid[row][col + 1]) //E
124 }
125 if row < grid.len() - 1 {
126 retval[2] = Some(grid[row + 1][col]) //S
127 }
128 if col > 0 {
129 retval[3] = Some(grid[row][col - 1]) //N
130 }
131
132 retval
133}
134
135/// Never Eat Shredded Wheat, rotated 45 degrees clockwise
136/// AKA northeast, southeast, southwest, northwest
137fn adjacent_ordinal<T: Copy>(grid: &[Vec<T>], coords: Point) -> [Option<T>; 4] {
138 let mut retval = [const { None }; 4];
139 let (row, col) = (coords.row, coords.col);
140
141 if row > 0 && col < grid[0].len() - 1 {
142 retval[0] = Some(grid[row - 1][col + 1]) //NE
143 }
144 if row < grid.len() - 1 && col < grid[0].len() - 1 {
145 retval[1] = Some(grid[row + 1][col + 1]) //SE
146 }
147 if row < grid.len() - 1 && col > 0 {
148 retval[2] = Some(grid[row + 1][col - 1]) //SW
149 }
150 if row > 0 && col > 0 {
151 retval[3] = Some(grid[row - 1][col - 1]) //NW
152 }
153
154 retval
155}
156
157pub fn transpose<S: AsRef<[T]>, T: Copy>(input: &[S]) -> Vec<Vec<T>> {
158 (0..input[0].as_ref().len())
159 .map(|col| input.iter().map(|row| row.as_ref()[col]).collect())
160 .collect()
161}