Skip to content

Commit af5b61a

Browse files
committed
Optimize 2024 day 12
The number of corners is quick to count and is equal to the number of sides
1 parent 76249e7 commit af5b61a

File tree

1 file changed

+61
-86
lines changed

1 file changed

+61
-86
lines changed

crates/year2024/src/day12.rs

Lines changed: 61 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,122 +4,97 @@ use utils::prelude::*;
44
/// Counting area, perimeter and sides of shapes in a grid.
55
#[derive(Clone, Debug)]
66
pub struct Day12 {
7-
grid: Vec<u8>,
8-
offsets: [isize; 4],
7+
part1: u32,
8+
part2: u32,
99
}
1010

1111
impl Day12 {
1212
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
1313
let (_, cols, grid) =
14-
grid::from_str_padded(input, 2, 0, |b| b.is_ascii_uppercase().then_some(b))?;
14+
grid::from_str_padded(input, 1, 0, |b| b.is_ascii_uppercase().then_some(b))?;
1515
let offsets = [-(cols as isize), 1, cols as isize, -1];
16-
Ok(Self { grid, offsets })
17-
}
1816

19-
#[must_use]
20-
pub fn part1(&self) -> u64 {
21-
let mut visited = vec![false; self.grid.len()];
22-
let mut total = 0;
23-
for i in 0..self.grid.len() {
24-
if self.grid[i] == 0 || visited[i] {
17+
let mut visited = vec![false; grid.len()];
18+
let (mut part1, mut part2) = (0, 0);
19+
for i in 0..grid.len() {
20+
if grid[i] == 0 || visited[i] {
2521
continue;
2622
}
2723

28-
let (area, perimeter) = self.flood_fill(i, &mut visited);
29-
total += area * perimeter;
30-
}
31-
total
32-
}
33-
34-
fn flood_fill(&self, i: usize, visited: &mut [bool]) -> (u64, u64) {
35-
let plant = self.grid[i];
36-
visited[i] = true;
37-
38-
let (mut area, mut perimeter) = (1, 0);
39-
for &offset in &self.offsets {
40-
let next = i.wrapping_add_signed(offset);
41-
if self.grid[next] == plant {
42-
if !visited[next] {
43-
let (a, p) = self.flood_fill(next, visited);
44-
area += a;
45-
perimeter += p;
46-
}
47-
} else {
48-
perimeter += 1;
49-
}
24+
let (area, perimeter, corners) = FloodFill::fill_shape(&grid, offsets, &mut visited, i);
25+
part1 += area * perimeter;
26+
part2 += area * corners;
5027
}
5128

52-
(area, perimeter)
29+
Ok(Self { part1, part2 })
5330
}
5431

5532
#[must_use]
56-
pub fn part2(&self) -> u64 {
57-
let mut visited = vec![false; self.grid.len()];
58-
let mut edges = vec![[false; 4]; self.grid.len()];
59-
let mut total = 0;
60-
for i in 0..self.grid.len() {
61-
if self.grid[i] == 0 || visited[i] {
62-
continue;
63-
}
33+
pub fn part1(&self) -> u32 {
34+
self.part1
35+
}
6436

65-
let (area, min_idx, max_idx) = self.edge_fill(i, &mut visited, &mut edges);
37+
#[must_use]
38+
pub fn part2(&self) -> u32 {
39+
self.part2
40+
}
41+
}
6642

67-
let mut sides = 0;
68-
for dir in 0..4 {
69-
for j in min_idx..=max_idx {
70-
if edges[j][dir] {
71-
sides += 1;
72-
self.edge_unset(j, dir, &mut edges);
73-
}
74-
}
75-
}
43+
struct FloodFill<'a> {
44+
grid: &'a [u8],
45+
offsets: [isize; 4],
46+
visited: &'a mut [bool],
47+
area: u32,
48+
perimeter: u32,
49+
corners: u32,
50+
}
7651

77-
total += area * sides;
78-
}
79-
total
52+
impl<'a> FloodFill<'a> {
53+
fn fill_shape(
54+
grid: &'a [u8],
55+
offsets: [isize; 4],
56+
visited: &'a mut [bool],
57+
i: usize,
58+
) -> (u32, u32, u32) {
59+
let mut instance = Self {
60+
grid,
61+
offsets,
62+
visited,
63+
area: 0,
64+
perimeter: 0,
65+
corners: 0,
66+
};
67+
instance.visit(i);
68+
(instance.area, instance.perimeter, instance.corners)
8069
}
8170

82-
fn edge_fill(
83-
&self,
84-
i: usize,
85-
visited: &mut [bool],
86-
edges: &mut [[bool; 4]],
87-
) -> (u64, usize, usize) {
71+
fn visit(&mut self, i: usize) {
8872
let plant = self.grid[i];
89-
visited[i] = true;
90-
91-
let (mut area, mut min_idx, mut max_idx) = (1, usize::MAX, 0);
92-
for dir in 0..4 {
93-
let next = i.wrapping_add_signed(self.offsets[dir]);
94-
if self.grid[next] == plant {
95-
if !visited[next] {
96-
let r = self.edge_fill(next, visited, edges);
97-
area += r.0;
98-
min_idx = min_idx.min(r.1);
99-
max_idx = max_idx.max(r.2);
73+
self.visited[i] = true;
74+
self.area += 1;
75+
76+
for d in 0..4 {
77+
let neighbour1 = i.wrapping_add_signed(self.offsets[d]);
78+
if self.grid[neighbour1] == plant {
79+
if !self.visited[neighbour1] {
80+
self.visit(neighbour1);
10081
}
10182
} else {
102-
edges[next][dir] = true;
103-
min_idx = min_idx.min(next);
104-
max_idx = max_idx.max(next);
83+
self.perimeter += 1;
10584
}
106-
}
107-
108-
(area, min_idx, max_idx)
109-
}
11085

111-
fn edge_unset(&self, i: usize, dir: usize, edges: &mut [[bool; 4]]) {
112-
edges[i][dir] = false;
113-
for offset in [self.offsets[(dir + 1) % 4], self.offsets[(dir + 3) % 4]] {
114-
let next = i.wrapping_add_signed(offset);
115-
if edges[next][dir] {
116-
self.edge_unset(next, dir, edges);
86+
let neighbour2 = i.wrapping_add_signed(self.offsets[(d + 1) % 4]);
87+
let between = i.wrapping_add_signed(self.offsets[d] + self.offsets[(d + 1) % 4]);
88+
if ((self.grid[neighbour1] == plant) == (self.grid[neighbour2] == plant))
89+
&& (self.grid[neighbour1] != plant || self.grid[between] != plant)
90+
{
91+
self.corners += 1;
11792
}
11893
}
11994
}
12095
}
12196

122-
examples!(Day12 -> (u64, u64) [
97+
examples!(Day12 -> (u32, u32) [
12398
{file: "day12_example0.txt", part1: 140, part2: 80},
12499
{file: "day12_example1.txt", part1: 772},
125100
{file: "day12_example2.txt", part1: 1930, part2: 1206},

0 commit comments

Comments
 (0)