Skip to content

Commit 1e884c5

Browse files
ianbanksBen Rampling
andauthored
Solve day 9 part 2 using a sweep line algorithm (#16)
Co-authored-by: Ben Rampling <ben@ucc.gu.uwa.edu.au>
1 parent 4ac7e43 commit 1e884c5

File tree

1 file changed

+125
-60
lines changed

1 file changed

+125
-60
lines changed

src/year2025/day09.rs

Lines changed: 125 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
use crate::util::grid::*;
2-
use crate::util::hash::*;
31
use crate::util::iter::*;
42
use crate::util::parse::*;
5-
use crate::util::point::*;
6-
use std::collections::VecDeque;
7-
8-
const OUTSIDE: i64 = 0;
9-
const INSIDE: i64 = 1;
10-
const UNKNOWN: i64 = 2;
113

124
type Tile = [u64; 2];
135

@@ -30,75 +22,148 @@ pub fn part1(tiles: &[Tile]) -> u64 {
3022
}
3123

3224
pub fn part2(tiles: &[Tile]) -> u64 {
33-
let size = tiles.len();
34-
let shrink_x = shrink(tiles, 0);
35-
let shrink_y = shrink(tiles, 1);
36-
let shrunk: Vec<_> = tiles.iter().map(|&[x, y]| (shrink_x[&x], shrink_y[&y])).collect();
25+
let mut tiles = tiles.to_vec();
3726

38-
let mut area = 0;
39-
let mut todo = VecDeque::from([ORIGIN]);
40-
let mut grid = Grid::new(shrink_x.len() as i32, shrink_y.len() as i32, UNKNOWN);
27+
tiles.sort_unstable_by_key(|&[x, y]| (y, x));
28+
29+
let tiles = tiles;
30+
31+
// Track the largest area so far during scanning:
32+
let mut largest_area: u64 = 0;
4133

42-
for i in 0..size {
43-
let (x1, y1, x2, y2) = minmax(shrunk[i], shrunk[(i + 1) % size]);
34+
// Each red tile (`x`, `y`) becomes a candidate for being a top corner of the largest area, and during the
35+
// scan the `interval` containing the maximum possible width is updated:
36+
struct Candidate {
37+
x: u64,
38+
y: u64,
39+
interval: Interval,
40+
}
41+
42+
let mut candidates: Vec<Candidate> = Vec::with_capacity(512);
43+
44+
// Maintain an ordered list of descending edges, i.e. [begin_interval_0, end_interval_0, begin_interval_1, end_interval_1, ...]:
45+
let mut descending_edges: Vec<u64> = vec![];
46+
let mut intervals_from_descending_edges = vec![];
47+
48+
// Invariants on the input data (defined by the puzzle) result in points arriving in pairs on the same y line:
49+
let mut it = tiles.into_iter();
50+
51+
while let (Some([x0, y]), Some([x1, y1])) = (it.next(), it.next()) {
52+
debug_assert_eq!(y, y1);
53+
54+
// Update the descending edges; since we are scanning from top to bottom, and within each line left to right,
55+
// when we, starting from outside of the region, hit a corner tile it is either:
56+
//
57+
// - The corner of two edges, one going right and one going down. In this case, the `descending_edges` won't contain
58+
// the `x` coordinate, and we should "toggle" it on to denote that there is a new descending edge.
59+
// - The corner of two edges, one going right and one going up. The `descending_edges` will contain an `x` coordinate,
60+
// that should be "toggled" off.
61+
//
62+
// Simular arguments work for when we are scanning inside the edge and we hit the corner that ends the edge; this is also
63+
// why corners always arrive in pairs.
64+
//
65+
// Do the update:
66+
for x in [x0, x1] {
67+
toggle_value_membership_in_ordered_list(&mut descending_edges, x);
68+
}
4469

45-
for x in x1..x2 + 1 {
46-
for y in y1..y2 + 1 {
47-
grid[Point::new(x, y)] = INSIDE;
70+
// Every pair of descending edges in the ordered list defines a region; find the resulting intervals on this line:
71+
update_intervals_from_descending_edges(
72+
&descending_edges,
73+
&mut intervals_from_descending_edges,
74+
);
75+
76+
// Check the rectangles this red tile could be a bottom tile for, with the current candidates:
77+
for candidate in candidates.iter() {
78+
for x in [x0, x1] {
79+
if candidate.interval.contains(x) {
80+
largest_area = largest_area
81+
.max((candidate.x.abs_diff(x) + 1) * (candidate.y.abs_diff(y) + 1));
82+
}
4883
}
4984
}
50-
}
5185

52-
while let Some(point) = todo.pop_front() {
53-
for next in ORTHOGONAL.map(|o| point + o) {
54-
if grid.contains(next) && grid[next] == UNKNOWN {
55-
grid[next] = OUTSIDE;
56-
todo.push_back(next);
86+
// Update candidates when their interval shrinks due to descending edge changes, and drop them when their interval becomes empty:
87+
candidates.retain_mut(|candidate| {
88+
if let Some(intersection_containing_x) =
89+
intervals_from_descending_edges.iter().find(|i| i.contains(candidate.x))
90+
{
91+
candidate.interval = intersection_containing_x.intersection(candidate.interval);
92+
93+
true
94+
} else {
95+
false
96+
}
97+
});
98+
99+
// Add any new candidates:
100+
for x in [x0, x1] {
101+
if let Some(&containing) =
102+
intervals_from_descending_edges.iter().find(|i| i.contains(x))
103+
{
104+
candidates.push(Candidate { x, y, interval: containing });
57105
}
58106
}
59107
}
60108

61-
for y in 1..grid.height {
62-
for x in 1..grid.width {
63-
let point = Point::new(x, y);
64-
let value = i64::from(grid[point] != OUTSIDE);
65-
grid[point] = value + grid[point + UP] + grid[point + LEFT] - grid[point + UP + LEFT];
66-
}
109+
largest_area
110+
}
111+
112+
/// The set { x in u64 | l <= x <= r }.
113+
#[derive(Clone, Copy)]
114+
struct Interval {
115+
l: u64,
116+
r: u64,
117+
}
118+
119+
impl Interval {
120+
fn new(l: u64, r: u64) -> Self {
121+
debug_assert!(l <= r);
122+
123+
Interval { l, r }
67124
}
68125

69-
for i in 0..size {
70-
for j in i + 1..size {
71-
let (x1, y1, x2, y2) = minmax(shrunk[i], shrunk[j]);
72-
73-
let expected = (x2 - x1 + 1) as i64 * (y2 - y1 + 1) as i64;
74-
let actual = grid[Point::new(x2, y2)]
75-
- grid[Point::new(x1 - 1, y2)]
76-
- grid[Point::new(x2, y1 - 1)]
77-
+ grid[Point::new(x1 - 1, y1 - 1)];
78-
79-
if expected == actual {
80-
let [x1, y1] = tiles[i];
81-
let [x2, y2] = tiles[j];
82-
let dx = x1.abs_diff(x2) + 1;
83-
let dy = y1.abs_diff(y2) + 1;
84-
area = area.max(dx * dy);
85-
}
86-
}
126+
fn intersects(self, other: Self) -> bool {
127+
other.l <= self.r && self.l <= other.r
87128
}
88129

89-
area
130+
fn intersection(self, other: Self) -> Self {
131+
debug_assert!(self.intersects(other));
132+
133+
Interval::new(self.l.max(other.l), self.r.min(other.r))
134+
}
135+
136+
fn contains(self, x: u64) -> bool {
137+
self.l <= x && x <= self.r
138+
}
90139
}
91140

92-
fn shrink(tiles: &[Tile], index: usize) -> FastMap<u64, i32> {
93-
let mut axis: Vec<_> = tiles.iter().map(|tile| tile[index]).collect();
94-
axis.push(u64::MIN);
95-
axis.push(u64::MAX);
96-
axis.sort_unstable();
97-
axis.dedup();
98-
axis.iter().enumerate().map(|(i, &n)| (n, i as i32)).collect()
141+
// Adds `value` if it isn't in `ordered_list`, removes it if it is, maintaining the order.
142+
fn toggle_value_membership_in_ordered_list(ordered_list: &mut Vec<u64>, value: u64) {
143+
let mut i = 0;
144+
145+
while i < ordered_list.len() && ordered_list[i] < value {
146+
i += 1;
147+
}
148+
149+
if i == ordered_list.len() || ordered_list[i] != value {
150+
ordered_list.insert(i, value);
151+
} else {
152+
ordered_list.remove(i);
153+
}
99154
}
100155

156+
// Changes the list of descending edges, [begin_interval_0, end_interval_0, begin_interval_1, end_interval_1, ...],
157+
// into a vector containing the intervals.
101158
#[inline]
102-
fn minmax((x1, y1): (i32, i32), (x2, y2): (i32, i32)) -> (i32, i32, i32, i32) {
103-
(x1.min(x2), y1.min(y2), x1.max(x2), y1.max(y2))
159+
fn update_intervals_from_descending_edges(descending_edges: &[u64], to_update: &mut Vec<Interval>) {
160+
debug_assert!(descending_edges.len().is_multiple_of(2));
161+
162+
to_update.clear();
163+
164+
let mut it = descending_edges.iter();
165+
166+
while let (Some(&l), Some(&r)) = (it.next(), it.next()) {
167+
to_update.push(Interval::new(l, r));
168+
}
104169
}

0 commit comments

Comments
 (0)