Skip to content

Commit 7ef2b78

Browse files
committed
2025: day 9 fast
1 parent 2640f7d commit 7ef2b78

File tree

5 files changed

+215
-50
lines changed

5 files changed

+215
-50
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
![Stars: 518](https://img.shields.io/badge/Stars-518⭐-blue)
44
![Rust: 259](https://img.shields.io/badge/Rust-259-cyan?logo=Rust)
5-
![Python: 128](https://img.shields.io/badge/Python-128-cyan?logo=Python)
5+
![Python: 127](https://img.shields.io/badge/Python-127-cyan?logo=Python)
66

77
<img src="./scripts/assets/christmas_ferris_2015_2024.png" alt="Christmas Ferris" width="164" />
88

@@ -22,13 +22,13 @@ Puzzle | Stars | Lang
2222
[Day 6: Trash Compactor](https://adventofcode.com/2025/day/6) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day6/day6.rs) [![Rust](./scripts/assets/rust.png)](src/year2025/day6/day6_declarative.rs) [![Python](./scripts/assets/python.png)](src/year2025/day6/day6.py)
2323
[Day 7: Laboratories](https://adventofcode.com/2025/day/7) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day7/day7.rs) [![Go](./scripts/assets/go.png)](src/year2025/day7/day7.go) [🎁](src/year2025/day7/README.md)
2424
[Day 8: Playground](https://adventofcode.com/2025/day/8) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day8/day8.rs)
25-
[Day 9: Movie Theater](https://adventofcode.com/2025/day/9) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day9/day9.rs) [![Python](./scripts/assets/python.png)](src/year2025/day9/day9.py) [🎁](src/year2025/day9/README.md)
25+
[Day 9: Movie Theater](https://adventofcode.com/2025/day/9) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day9/day9.rs) [![Rust](./scripts/assets/rust.png)](src/year2025/day9/day9_geo.rs) [![Python](./scripts/assets/python.png)](src/year2025/day9/day9_img.py) [🎁](src/year2025/day9/README.md)
2626

2727
## All years
2828

2929
Calendar | Solutions | Stars | Rust | Python | 🎁
3030
-------- | --------- | ----- | ---- | ------ | --
31-
[Advent of Code 2025](https://adventofcode.com/2025) | [Solutions](src/year2025/README.md) | 18⭐ | 9 | 6 | 2
31+
[Advent of Code 2025](https://adventofcode.com/2025) | [Solutions](src/year2025/README.md) | 18⭐ | 9 | 5 | 2
3232
[Advent of Code 2024](https://adventofcode.com/2024) | [Solutions](src/year2024/README.md) | 50⭐ | 25 | 11 | 3
3333
[Advent of Code 2023](https://adventofcode.com/2023) | [Solutions](src/year2023/README.md) | 50⭐ | 25 | 10 | 2
3434
[Advent of Code 2022](https://adventofcode.com/2022) | [Solutions](src/year2022/README.md) | 50⭐ | 25 | 18 | 1

src/year2025/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
![AoC2025](https://img.shields.io/badge/Advent_of_Code-2025-8A2BE2)
44
![Stars: 18](https://img.shields.io/badge/Stars-18⭐-blue)
5-
![Rust: 10](https://img.shields.io/badge/Rust-10-cyan?logo=Rust)
5+
![Rust: 11](https://img.shields.io/badge/Rust-11-cyan?logo=Rust)
66
![Python: 6](https://img.shields.io/badge/Python-6-cyan?logo=Python)
77

88
## 2025 ([Calendar](https://adventofcode.com/2025)) ([Solutions](./)) : 18⭐
@@ -17,4 +17,4 @@ Puzzle | Stars | Lang
1717
[Day 6: Trash Compactor](https://adventofcode.com/2025/day/6) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day6/day6.rs) [![Rust](../../scripts/assets/rust.png)](day6/day6_declarative.rs) [![Python](../../scripts/assets/python.png)](day6/day6.py)
1818
[Day 7: Laboratories](https://adventofcode.com/2025/day/7) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day7/day7.rs) [![Go](../../scripts/assets/go.png)](day7/day7.go) [🎁](day7/README.md)
1919
[Day 8: Playground](https://adventofcode.com/2025/day/8) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day8/day8.rs)
20-
[Day 9: Movie Theater](https://adventofcode.com/2025/day/9) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day9/day9.rs) [![Python](../../scripts/assets/python.png)](day9/day9.py) [🎁](day9/README.md)
20+
[Day 9: Movie Theater](https://adventofcode.com/2025/day/9) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day9/day9.rs) [![Rust](../../scripts/assets/rust.png)](day9/day9_geo.rs) [![Python](../../scripts/assets/python.png)](day9/day9_img.py) [🎁](day9/README.md)

src/year2025/day9/day9.rs

Lines changed: 96 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
//! [Day 9: Movie Theater](https://adventofcode.com/2025/day/9)
22
3-
// Nota: not a good solution 😡 But it solves the puzzle...
4-
5-
use geo::algorithm::contains::Contains;
6-
use geo::{Point, Polygon, Rect};
73
use itertools::Itertools;
84

5+
struct Point {
6+
x: i32,
7+
y: i32,
8+
}
99
struct Puzzle {
1010
points: Vec<Point>,
1111
}
@@ -19,32 +19,25 @@ impl Puzzle {
1919
.map(|line| {
2020
let (x, y) = line
2121
.split(',')
22-
.map(|x| x.parse::<f64>().unwrap())
22+
.map(|x| x.parse::<i32>().unwrap())
2323
.collect_tuple()
2424
.unwrap();
2525

26-
Point::new(x, y)
26+
Point { x, y }
2727
})
2828
.collect(),
2929
}
3030
}
3131

3232
/// Solve part one.
33-
fn part1(&self) -> f64 {
34-
let mut max_area = 0.;
35-
36-
for (i, p1) in self.points.iter().enumerate() {
37-
for (j, p2) in self.points.iter().enumerate() {
38-
if i > j {
39-
let xmin = p1.x().min(p2.x());
40-
let xmax = p1.x().max(p2.x());
41-
let ymin = p1.y().min(p2.y());
42-
let ymax = p1.y().max(p2.y());
43-
44-
let area = (xmax + 1. - xmin) * (ymax + 1. - ymin);
45-
if area > max_area {
46-
max_area = area;
47-
}
33+
fn part1(&self) -> u64 {
34+
let mut max_area = 0;
35+
36+
for (i, a) in self.points.iter().enumerate() {
37+
for b in self.points.iter().skip(i + 1) {
38+
let area = (u64::from(a.x.abs_diff(b.x)) + 1) * (u64::from(a.y.abs_diff(b.y)) + 1);
39+
if area > max_area {
40+
max_area = area;
4841
}
4942
}
5043
}
@@ -53,27 +46,85 @@ impl Puzzle {
5346
}
5447

5548
/// Solve part two.
56-
fn part2(&self) -> f64 {
57-
let poly = Polygon::new(self.points.clone().into(), vec![]);
58-
59-
let mut max_area = 0.;
60-
61-
for (i, p1) in self.points.iter().enumerate() {
62-
for (j, p2) in self.points.iter().enumerate() {
63-
if i > j {
64-
let xmin = p1.x().min(p2.x());
65-
let xmax = p1.x().max(p2.x());
66-
let ymin = p1.y().min(p2.y());
67-
let ymax = p1.y().max(p2.y());
68-
69-
let rect = Rect::new((xmin, ymin), (xmax, ymax));
70-
71-
if poly.contains(&rect) {
72-
let area = (xmax + 1. - xmin) * (ymax + 1. - ymin);
73-
if area > max_area {
74-
max_area = area;
75-
}
76-
}
49+
fn part2(&self) -> u64 {
50+
let mut max_area = 0;
51+
let n = self.points.len();
52+
53+
// If the first edge (p0-p1) is vertical, the next edge (p1-p2) is horizontal.
54+
// So, we have to use points 1,3,5,... for the horizontal edges and 0,2,4... for the v ones.
55+
// Otherwise, it is the opposite.
56+
let start = usize::from(self.points[0].x == self.points[1].x);
57+
58+
let horizontal_edges = self
59+
.points
60+
.iter()
61+
.enumerate()
62+
.skip(start)
63+
.step_by(2)
64+
.map(|(i, p)| {
65+
let x1 = p.x;
66+
let x2 = self.points[(i + 1) % n].x;
67+
(x1.min(x2), x1.max(x2), p.y)
68+
})
69+
.collect::<Vec<_>>();
70+
71+
let vertical_edges = self
72+
.points
73+
.iter()
74+
.enumerate()
75+
.skip(1 - start)
76+
.step_by(2)
77+
.map(|(i, p)| {
78+
let y1 = p.y;
79+
let y2 = self.points[(i + 1) % n].y;
80+
(p.x, y1.min(y2), y1.max(y2))
81+
})
82+
.collect::<Vec<_>>();
83+
84+
let is_ok = |a: &Point, b: &Point| -> bool {
85+
let x1 = a.x.min(b.x);
86+
let x2 = a.x.max(b.x);
87+
let y1 = a.y.min(b.y);
88+
let y2 = a.y.max(b.y);
89+
90+
//
91+
// y1 → Axxxxx If there is an horizontal edge between
92+
// x x the top and bottom of the rectangle,
93+
// x x it should be entirely at the left or at the right
94+
// ey → hhhh x but not overlap the horizontal side of the rectangle.
95+
// x x If this is the case, either the top or bottom of
96+
// y2 → xxxxxB the edge is outside the polygon.
97+
// ↑ ↑
98+
// x1 x2 ❌ The configuration at the left is invalid.
99+
//
100+
// y1 → Axxxxx
101+
// x x hhhhh
102+
// x x
103+
// hhh x x ✅ These both configurations are ok.
104+
// x x
105+
// y2 → xxxxxB
106+
//
107+
for &(edge_x1, edge_x2, edge_y) in &horizontal_edges {
108+
if y1 < edge_y && edge_y < y2 && x1 < edge_x2 && edge_x1 < x2 {
109+
return false;
110+
}
111+
}
112+
113+
// Same thing with vertical edges.
114+
for &(edge_x, edge_y1, edge_y2) in &vertical_edges {
115+
if x1 < edge_x && edge_x < x2 && y1 < edge_y2 && edge_y1 < y2 {
116+
return false;
117+
}
118+
}
119+
120+
true
121+
};
122+
123+
for (i, a) in self.points.iter().enumerate() {
124+
for b in self.points.iter().skip(i + 1) {
125+
let area = (u64::from(a.x.abs_diff(b.x)) + 1) * (u64::from(a.y.abs_diff(b.y)) + 1);
126+
if area > max_area && is_ok(a, b) {
127+
max_area = area;
77128
}
78129
}
79130
}
@@ -84,7 +135,7 @@ impl Puzzle {
84135

85136
/// # Panics
86137
#[must_use]
87-
pub fn solve(data: &str) -> (f64, f64) {
138+
pub fn solve(data: &str) -> (u64, u64) {
88139
let puzzle = Puzzle::new(data);
89140
(puzzle.part1(), puzzle.part2())
90141
}
@@ -103,12 +154,12 @@ mod test {
103154
#[test]
104155
fn part1() {
105156
let puzzle = Puzzle::new(TEST_INPUT);
106-
assert_eq!(puzzle.part1(), 50.);
157+
assert_eq!(puzzle.part1(), 50);
107158
}
108159

109160
#[test]
110161
fn part2() {
111162
let puzzle = Puzzle::new(TEST_INPUT);
112-
assert_eq!(puzzle.part2(), 24.);
163+
assert_eq!(puzzle.part2(), 24);
113164
}
114165
}

src/year2025/day9/day9_geo.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//! [Day 9: Movie Theater](https://adventofcode.com/2025/day/9)
2+
3+
// Nota: not a good solution 😡 But it solves the puzzle...
4+
5+
use geo::algorithm::contains::Contains;
6+
use geo::{Point, Polygon, Rect};
7+
use itertools::Itertools;
8+
9+
struct Puzzle {
10+
points: Vec<Point>,
11+
}
12+
13+
impl Puzzle {
14+
/// Initialize from the puzzle input.
15+
fn new(data: &str) -> Self {
16+
Self {
17+
points: data
18+
.lines()
19+
.map(|line| {
20+
let (x, y) = line
21+
.split(',')
22+
.map(|x| x.parse::<f64>().unwrap())
23+
.collect_tuple()
24+
.unwrap();
25+
26+
Point::new(x, y)
27+
})
28+
.collect(),
29+
}
30+
}
31+
32+
/// Solve part one.
33+
fn part1(&self) -> f64 {
34+
let mut max_area = 0.;
35+
36+
for (i, p1) in self.points.iter().enumerate() {
37+
for (j, p2) in self.points.iter().enumerate() {
38+
if i > j {
39+
let xmin = p1.x().min(p2.x());
40+
let xmax = p1.x().max(p2.x());
41+
let ymin = p1.y().min(p2.y());
42+
let ymax = p1.y().max(p2.y());
43+
44+
let area = (xmax + 1. - xmin) * (ymax + 1. - ymin);
45+
if area > max_area {
46+
max_area = area;
47+
}
48+
}
49+
}
50+
}
51+
52+
max_area
53+
}
54+
55+
/// Solve part two.
56+
fn part2(&self) -> f64 {
57+
let poly = Polygon::new(self.points.clone().into(), vec![]);
58+
59+
let mut max_area = 0.;
60+
61+
for (i, p1) in self.points.iter().enumerate() {
62+
for (j, p2) in self.points.iter().enumerate() {
63+
if i > j {
64+
let xmin = p1.x().min(p2.x());
65+
let xmax = p1.x().max(p2.x());
66+
let ymin = p1.y().min(p2.y());
67+
let ymax = p1.y().max(p2.y());
68+
69+
let rect = Rect::new((xmin, ymin), (xmax, ymax));
70+
71+
if poly.contains(&rect) {
72+
let area = (xmax + 1. - xmin) * (ymax + 1. - ymin);
73+
if area > max_area {
74+
max_area = area;
75+
}
76+
}
77+
}
78+
}
79+
}
80+
81+
max_area
82+
}
83+
}
84+
85+
/// # Panics
86+
#[must_use]
87+
pub fn solve(data: &str) -> (f64, f64) {
88+
let puzzle = Puzzle::new(data);
89+
(puzzle.part1(), puzzle.part2())
90+
}
91+
92+
pub fn main() {
93+
let args = aoc::parse_args();
94+
args.run(solve);
95+
}
96+
97+
#[cfg(test)]
98+
mod test {
99+
use super::*;
100+
101+
const TEST_INPUT: &str = include_str!("test.txt");
102+
103+
#[test]
104+
fn part1() {
105+
let puzzle = Puzzle::new(TEST_INPUT);
106+
assert_eq!(puzzle.part1(), 50.);
107+
}
108+
109+
#[test]
110+
fn part2() {
111+
let puzzle = Puzzle::new(TEST_INPUT);
112+
assert_eq!(puzzle.part2(), 24.);
113+
}
114+
}

0 commit comments

Comments
 (0)