Skip to content

Commit 3280843

Browse files
committed
chore: WIP optimization of y2025::day_09::part_2
1 parent bd7bf5b commit 3280843

File tree

3 files changed

+168
-53
lines changed

3 files changed

+168
-53
lines changed

.run/run_aoc.run.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<component name="ProjectRunConfigurationManager">
22
<configuration default="false" name="run_aoc" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
33
<option name="buildProfileId" value="dev" />
4-
<option name="command" value="run --package aoclp_solutions --bin aoclp_solutions -- --year 2025 --day 10" />
4+
<option name="command" value="run --package aoclp_solutions --bin aoclp_solutions -- --year 2025 --day 9" />
55
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
66
<envs />
77
<option name="emulateTerminal" value="true" />

aoclp/src/positioning/pt.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
use std::cmp::{max, min};
12
use std::collections::HashMap;
23
use std::convert::Infallible;
34
use std::fmt::{Debug, Display, Formatter};
45
use std::hash::Hash;
5-
use std::ops::{Add, AddAssign, RangeBounds, Sub, SubAssign};
6+
use std::ops::{Add, AddAssign, Mul, MulAssign, RangeBounds, Sub, SubAssign};
67
use std::str::FromStr;
78
use std::sync::OnceLock;
89

@@ -154,6 +155,27 @@ where
154155
}
155156
}
156157

158+
impl<T> Mul<T> for Pt<T>
159+
where
160+
T: Mul<Output = T> + Copy,
161+
{
162+
type Output = Self;
163+
164+
fn mul(self, rhs: T) -> Self::Output {
165+
Self::new(self.x * rhs, self.y * rhs)
166+
}
167+
}
168+
169+
impl<T> MulAssign<T> for Pt<T>
170+
where
171+
T: MulAssign + Copy,
172+
{
173+
fn mul_assign(&mut self, rhs: T) {
174+
self.x *= rhs;
175+
self.y *= rhs;
176+
}
177+
}
178+
157179
impl<T> Zero for Pt<T>
158180
where
159181
T: Zero,
@@ -177,6 +199,42 @@ where
177199
(a.x - b.x).abs() + (a.y - b.y).abs()
178200
}
179201

202+
/// Given two arbitrary points `a` and `b` in 2D space, returns the point with the
203+
/// minimum `x` and `y` values of `a` and `b`, and the point with the maximum
204+
/// `x` and `y` values of `a` and `b`.
205+
///
206+
/// Put another way, if we see `a` and `b` as being any two opposite corners of a
207+
/// rectangle, then this function returns the top-left and bottom-right corners of
208+
/// that rectangle, in that order.
209+
pub fn min_max<T>(a: Pt<T>, b: Pt<T>) -> (Pt<T>, Pt<T>)
210+
where
211+
T: Ord + Copy,
212+
{
213+
(
214+
Pt::new(min(a.x, b.x), min(a.y, b.y)),
215+
Pt::new(max(a.x, b.x), max(a.y, b.y)),
216+
)
217+
}
218+
219+
/// Given two points representing any two opposite corners of a rectangle in 2D space,
220+
/// returns points representing the four corners of that rectangle, in this order:
221+
///
222+
/// - top-left
223+
/// - top-right
224+
/// - bottom-right
225+
/// - bottom-left
226+
pub fn rectangle_corners<T>(a: Pt<T>, b: Pt<T>) -> [Pt<T>; 4]
227+
where
228+
T: Ord + Copy,
229+
{
230+
[
231+
Pt::new(min(a.x, b.x), min(a.y, b.y)),
232+
Pt::new(max(a.x, b.x), min(a.y, b.y)),
233+
Pt::new(max(a.x, b.x), max(a.y, b.y)),
234+
Pt::new(min(a.x, b.x), max(a.y, b.y)),
235+
]
236+
}
237+
180238
/// Returns the size of the rectangle formed between two points in 2D space,
181239
/// as if the points were two of the rectangle's opposing corners.
182240
pub fn rectangular_area<T>(a: Pt<T>, b: Pt<T>) -> T

aoclp_solutions/src/y2025/day_09.rs

Lines changed: 108 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
use std::cmp::{max, min};
2-
use std::collections::BTreeSet;
3-
use std::iter::successors;
1+
use std::fmt;
2+
use std::iter::once;
43

54
use aoclp::positioning::direction::four_points::Direction4;
65
use aoclp::positioning::direction::{Direction, MovementDirection};
7-
use aoclp::positioning::pt::{rectangular_area, Pt};
6+
use aoclp::positioning::pt::{min_max, rectangle_corners, rectangular_area, Pt};
87
use aoclp::solvers_impl::input::safe_get_input_as_many;
98
use itertools::Itertools;
10-
use rayon::iter::{IntoParallelIterator, ParallelIterator};
119
use strum::IntoEnumIterator;
1210

1311
pub fn part_1() -> i64 {
@@ -21,40 +19,108 @@ pub fn part_1() -> i64 {
2119

2220
pub fn part_2() -> i64 {
2321
let red_tiles = input();
24-
25-
let red_zone: BTreeSet<_> = build_red_zone(&red_tiles).collect();
26-
let safe_rectangle = |a: Pt, b: Pt| {
27-
let corners = vec![
28-
Pt::new(min(a.x, b.x), min(a.y, b.y)),
29-
Pt::new(max(a.x, b.x), min(a.y, b.y)),
30-
Pt::new(max(a.x, b.x), max(a.y, b.y)),
31-
Pt::new(min(a.x, b.x), max(a.y, b.y)),
32-
Pt::new(min(a.x, b.x), min(a.y, b.y)),
33-
];
34-
let edges: BTreeSet<_> = corners
35-
.into_iter()
36-
.tuple_windows()
37-
.flat_map(|(a, b)| {
38-
let displacement = Pt::new((b.x - a.x).signum(), (b.y - a.y).signum());
39-
successors(Some(a), move |&p| (p != b).then_some(p + displacement))
40-
})
41-
.collect();
42-
edges.is_disjoint(&red_zone)
43-
};
22+
let walls = walls(&red_tiles).collect_vec();
4423

4524
red_tiles
4625
.into_iter()
4726
.array_combinations()
48-
.map(|[a, b]| (a, b, rectangular_area(a, b)))
49-
.sorted_unstable_by(|(_, _, area_a), (_, _, area_b)| area_b.cmp(area_a))
50-
.collect_vec()
51-
.into_par_iter()
52-
.find_first(|(a, b, _)| safe_rectangle(*a, *b))
53-
.map(|(_, _, area)| area)
27+
.filter(|[a, b]| {
28+
let corners = rectangle_corners(*a, *b);
29+
corners
30+
.into_iter()
31+
.chain(once(corners[0]))
32+
.tuple_windows()
33+
.map(|(a, b)| GridLine::from_endpoints(a, b))
34+
.all(|line| !walls.iter().any(|w| w.intersects(line)))
35+
})
36+
.map(|[a, b]| rectangular_area(a, b))
37+
.max()
5438
.unwrap()
5539
}
5640

57-
fn build_red_zone(red_tiles: &[Pt]) -> impl Iterator<Item = Pt> + use<'_> {
41+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
42+
enum GridLine {
43+
Horizontal {
44+
y: i64,
45+
left_x: i64,
46+
right_x: i64,
47+
},
48+
Vertical {
49+
x: i64,
50+
top_y: i64,
51+
bottom_y: i64,
52+
},
53+
Point(Pt),
54+
}
55+
56+
impl GridLine {
57+
fn from_endpoints(a: Pt, b: Pt) -> Self {
58+
let (a, b) = min_max(a, b);
59+
match (a.x == b.x, a.y == b.y) {
60+
(true, true) => Self::Point(a),
61+
(true, false) => Self::Vertical { x: a.x, top_y: a.y, bottom_y: b.y },
62+
(false, true) => Self::Horizontal { y: a.y, left_x: a.x, right_x: b.x },
63+
(false, false) => panic!("{a} and {b} do not form a line snapped to the grid"),
64+
}
65+
}
66+
67+
fn extend(self, direction: Direction4, len: i64) -> Self {
68+
match (self, direction) {
69+
(Self::Horizontal { y, left_x, right_x }, Direction4::Left) => {
70+
Self::Horizontal { y, left_x: left_x - len, right_x }
71+
},
72+
(Self::Horizontal { y, left_x, right_x }, Direction4::Right) => {
73+
Self::Horizontal { y, left_x, right_x: right_x + len}
74+
},
75+
(Self::Vertical { x, top_y, bottom_y }, Direction4::Up) => {
76+
Self::Vertical { x, top_y: top_y - len, bottom_y }
77+
},
78+
(Self::Vertical { x, top_y, bottom_y }, Direction4::Down) => {
79+
Self::Vertical { x, top_y, bottom_y: bottom_y + len }
80+
},
81+
(Self::Point(p), direction) => {
82+
Self::from_endpoints(p, p + (direction.displacement() * len))
83+
},
84+
(line, direction) => {
85+
panic!("line {line} cannot be extended {direction}");
86+
},
87+
}
88+
}
89+
90+
fn intersects(self, rhs: Self) -> bool {
91+
match (self, rhs) {
92+
(Self::Horizontal { y, left_x, right_x }, Self::Vertical { x, top_y, bottom_y }) |
93+
(Self::Vertical { x, top_y, bottom_y }, Self::Horizontal { y, left_x, right_x }) => {
94+
(top_y..=bottom_y).contains(&y) && (left_x..=right_x).contains(&x)
95+
},
96+
(Self::Horizontal { y, left_x, right_x }, Self::Point(p)) |
97+
(Self::Point(p), Self::Horizontal { y, left_x, right_x }) => {
98+
p.y == y && (left_x..=right_x).contains(&p.x)
99+
},
100+
(Self::Vertical { x, top_y, bottom_y }, Self::Point(p)) |
101+
(Self::Point(p), Self::Vertical { x, top_y, bottom_y }) => {
102+
p.x == x && (top_y..=bottom_y).contains(&p.y)
103+
},
104+
_ => false,
105+
}
106+
}
107+
}
108+
109+
impl fmt::Display for GridLine {
110+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111+
match *self {
112+
Self::Horizontal { y, left_x, right_x } => {
113+
write!(f, "{} - {}", Pt::new(left_x, y), Pt::new(right_x, y))
114+
},
115+
Self::Vertical { x, top_y, bottom_y } => {
116+
write!(f, "{} - {}", Pt::new(x, top_y), Pt::new(x, bottom_y))
117+
},
118+
Self::Point(p) => write!(f, "{p} - {p}"),
119+
}
120+
}
121+
}
122+
123+
fn walls(red_tiles: &[Pt]) -> impl Iterator<Item = GridLine> + use<'_> {
58124
let starting_point = red_tiles
59125
.iter()
60126
.sorted_by(|a, b| a.x.cmp(&b.x).then(a.y.cmp(&b.y)))
@@ -76,27 +142,18 @@ fn build_red_zone(red_tiles: &[Pt]) -> impl Iterator<Item = Pt> + use<'_> {
76142
.skip_while(move |&p| p != starting_point)
77143
.take(red_tiles.len() + 2)
78144
.tuple_windows()
79-
.flat_map(move |(a, b, c)| {
145+
.map(move |(a, b, c)| {
80146
let direction = get_direction(a, b);
81-
let turning_left = get_direction(b, c) == direction.turn_left();
82-
83-
let tail = if turning_left {
84-
vec![]
85-
} else {
86-
vec![
87-
b + direction.turn_left(),
88-
b + direction + direction.turn_left(),
89-
b + direction,
90-
]
91-
};
147+
let turning_right = get_direction(b, c) == direction.turn_right();
92148

93-
successors(Some(a), move |&p| {
94-
let next = p + direction;
95-
(next != b).then_some(next)
96-
})
97-
.skip(1)
98-
.map(move |p| p + (direction.turn_left()))
99-
.chain(tail)
149+
let mut line = GridLine::from_endpoints(
150+
a + direction + direction.turn_left(),
151+
b + direction.turn_around() + direction.turn_left(),
152+
);
153+
if turning_right {
154+
line = line.extend(direction, 2);
155+
}
156+
line
100157
})
101158
}
102159

0 commit comments

Comments
 (0)