Skip to content

Commit b546dcb

Browse files
committed
feat(09/2025): solve second part for example, too slow for input
1 parent c17607e commit b546dcb

File tree

7 files changed

+369
-6
lines changed

7 files changed

+369
-6
lines changed

src/solutions/year2025/day09.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,46 @@
11
use crate::solutions::Solution;
2+
use crate::utils::filled_region::FilledRegion;
23
use crate::utils::point::Point;
4+
use crate::utils::polygon::Polygon;
35
use crate::utils::surface_range::SurfaceRange;
6+
use crate::utils::traits::IsInside;
47
use itertools::Itertools;
58

69
pub struct Day09;
710

811
impl Solution for Day09 {
912
fn part_one(&self, input: &str) -> String {
1013
self.parse(input)
11-
.into_iter()
1214
.tuple_combinations()
1315
.map(|(a, b)| SurfaceRange::from((a, b)).area())
1416
.max()
1517
.unwrap()
1618
.to_string()
1719
}
1820

19-
fn part_two(&self, _input: &str) -> String {
20-
String::from("0")
21+
fn part_two(&self, input: &str) -> String {
22+
let points = self.parse(input);
23+
let polygon = points.clone().collect::<Polygon>();
24+
25+
points
26+
.tuple_combinations()
27+
.filter_map(|(a, b)| {
28+
let rectangle = Polygon::rectangle(a, b);
29+
if polygon.is_inside(&rectangle) {
30+
return Some(FilledRegion::from(rectangle).area()); // optimize...
31+
}
32+
33+
None
34+
})
35+
.max()
36+
.unwrap()
37+
.to_string()
2138
}
2239
}
2340

2441
impl Day09 {
25-
fn parse(&self, input: &str) -> Vec<Point> {
26-
input.lines().map(|line| line.parse().unwrap()).collect()
42+
fn parse<'a>(&self, input: &'a str) -> impl Iterator<Item = Point> + Clone + 'a {
43+
input.lines().map(|line| line.parse().unwrap())
2744
}
2845
}
2946

@@ -45,4 +62,9 @@ mod tests {
4562
fn part_one_example_test() {
4663
assert_eq!("50", Day09.part_one(EXAMPLE));
4764
}
65+
66+
#[test]
67+
fn part_two_example_test() {
68+
assert_eq!("24", Day09.part_two(EXAMPLE));
69+
}
4870
}

src/utils/filled_region.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::utils::direction::Direction;
22
use crate::utils::point::Point;
3+
use crate::utils::polygon::Polygon;
4+
use crate::utils::traits::IsInside;
35
use std::collections::HashSet;
46

57
/// Represents a contiguous region of points with a filled body.
@@ -92,11 +94,40 @@ impl FilledRegion {
9294
}
9395
}
9496

97+
impl From<Polygon> for FilledRegion {
98+
fn from(polygon: Polygon) -> Self {
99+
let mut points = HashSet::new();
100+
let polygon_points = polygon.points();
101+
let min_x = polygon_points.iter().map(|p| p.x).min().unwrap();
102+
let max_x = polygon_points.iter().map(|p| p.x).max().unwrap();
103+
let min_y = polygon_points.iter().map(|p| p.y).min().unwrap();
104+
let max_y = polygon_points.iter().map(|p| p.y).max().unwrap();
105+
106+
for y in min_y..=max_y {
107+
for x in min_x..=max_x {
108+
let point = Point::new(x, y);
109+
if polygon.is_inside(&point) {
110+
points.insert(point);
111+
}
112+
}
113+
}
114+
115+
Self { points }
116+
}
117+
}
118+
119+
impl IsInside<Polygon> for FilledRegion {
120+
fn is_inside(&self, value: &Polygon) -> bool {
121+
value.points().iter().all(|p| self.points.contains(p))
122+
}
123+
}
124+
95125
#[cfg(test)]
96126
mod test {
97127
use crate::utils::filled_region::FilledRegion;
98128
use crate::utils::grid::Grid;
99129
use crate::utils::point::Point;
130+
use crate::utils::polygon::Polygon;
100131
use std::collections::HashSet;
101132

102133
#[test]
@@ -241,6 +272,21 @@ AAAAAA"#;
241272
assert_eq!(12, filled_region.perimeter());
242273
}
243274

275+
#[test]
276+
fn from_polygon() {
277+
let points = [
278+
Point::new(0, 0),
279+
Point::new(0, 3),
280+
Point::new(2, 3),
281+
Point::new(2, 0),
282+
];
283+
284+
let polygon: Polygon = points.into_iter().collect();
285+
let region = FilledRegion::from(polygon);
286+
287+
assert_eq!(12, region.area());
288+
}
289+
244290
// todo: move get filled region to grid??
245291
// todo: handle multiple filled regions with the same element
246292
fn filled_region_from_grid(grid: &Grid<char>, element: char) -> FilledRegion {

src/utils/line.rs

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use crate::utils::orientation::Orientation;
12
use crate::utils::point::Point;
23

3-
#[derive(Debug, Copy, Clone)]
4+
#[derive(Debug, Copy, Clone, PartialEq)]
45
pub struct Line {
56
start: Point,
67
end: Point,
@@ -46,6 +47,52 @@ impl Line {
4647
Some(Point::new(x as isize, y as isize))
4748
}
4849

50+
fn orientation(&self) -> Option<Orientation> {
51+
let a = self.start;
52+
let b = self.end;
53+
54+
if a.x == b.x {
55+
return Some(Orientation::Vertical);
56+
} else if a.y == b.y {
57+
return Some(Orientation::Horizontal);
58+
}
59+
60+
None
61+
}
62+
63+
pub fn is_vertical(&self) -> bool {
64+
self.orientation() == Some(Orientation::Vertical)
65+
}
66+
67+
pub fn points(&self) -> Vec<Point> {
68+
if let Some(orientation) = self.orientation() {
69+
let start = self.start;
70+
let end = self.end;
71+
72+
return match orientation {
73+
Orientation::Horizontal => {
74+
let mut points = Vec::new();
75+
// todo range from unordered ?
76+
for x in start.x.min(end.x)..=start.x.max(end.x) {
77+
points.push(Point::new(x, start.y));
78+
}
79+
80+
points
81+
}
82+
Orientation::Vertical => {
83+
let mut points = Vec::new();
84+
for y in start.y.min(end.y)..=start.y.max(end.y) {
85+
points.push(Point::new(start.x, y));
86+
}
87+
88+
points
89+
}
90+
};
91+
}
92+
93+
unimplemented!("Only horizontal and vertical lines are supported")
94+
}
95+
4996
#[allow(dead_code)]
5097
pub fn is_on(&self, point: &Point) -> bool {
5198
let a = self.start;
@@ -77,4 +124,52 @@ mod tests {
77124
assert!(!line.is_on(&Point::new(15, 15))); // Outside the segment
78125
assert!(!line.is_on(&Point::new(1, 5))); // Not on the line (not collinear)
79126
}
127+
128+
#[test]
129+
fn points_horizontal() {
130+
let line = Line::new(Point::new(1, 3), Point::new(4, 3));
131+
let expected = vec![
132+
Point::new(1, 3),
133+
Point::new(2, 3),
134+
Point::new(3, 3),
135+
Point::new(4, 3),
136+
];
137+
assert_eq!(line.points(), expected);
138+
}
139+
140+
#[test]
141+
fn points_horizontal_reversed() {
142+
let line = Line::new(Point::new(4, 3), Point::new(1, 3));
143+
let expected = vec![
144+
Point::new(1, 3),
145+
Point::new(2, 3),
146+
Point::new(3, 3),
147+
Point::new(4, 3),
148+
];
149+
assert_eq!(line.points(), expected);
150+
}
151+
152+
#[test]
153+
fn points_vertical() {
154+
let line = Line::new(Point::new(2, 1), Point::new(2, 4));
155+
let expected = vec![
156+
Point::new(2, 1),
157+
Point::new(2, 2),
158+
Point::new(2, 3),
159+
Point::new(2, 4),
160+
];
161+
assert_eq!(line.points(), expected);
162+
}
163+
164+
#[test]
165+
fn points_vertical_reversed() {
166+
let line = Line::new(Point::new(2, 4), Point::new(2, 1));
167+
let expected = vec![
168+
Point::new(2, 1),
169+
Point::new(2, 2),
170+
Point::new(2, 3),
171+
Point::new(2, 4),
172+
];
173+
assert_eq!(line.points(), expected);
174+
}
80175
}

src/utils/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ pub mod grid;
77
pub mod line;
88
pub mod math;
99
pub mod moving_point;
10+
mod orientation;
1011
pub mod pair_generator;
1112
pub mod point;
1213
pub mod point3d;
14+
pub mod polygon;
1315
pub mod range;
1416
pub mod shoelace_formula;
1517
pub mod surface_range;
18+
pub mod traits;
1619
pub mod vector;

src/utils/orientation.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#[derive(PartialEq)]
2+
pub enum Orientation {
3+
Horizontal,
4+
Vertical,
5+
}

0 commit comments

Comments
 (0)