Skip to content

Commit 0a64367

Browse files
committed
Faster simpler approach
1 parent 6eaba1c commit 0a64367

File tree

2 files changed

+76
-42
lines changed

2 files changed

+76
-42
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
8383
| 9 | [Disk Fragmenter](https://adventofcode.com/2024/day/9) | [Source](src/year2024/day09.rs) | 106 |
8484
| 10 | [Hoof It](https://adventofcode.com/2024/day/10) | [Source](src/year2024/day10.rs) | 38 |
8585
| 11 | [Plutonian Pebbles](https://adventofcode.com/2024/day/11) | [Source](src/year2024/day11.rs) | 248 |
86-
| 12 | [Garden Groups](https://adventofcode.com/2024/day/12) | [Source](src/year2024/day12.rs) | 1582 |
86+
| 12 | [Garden Groups](https://adventofcode.com/2024/day/12) | [Source](src/year2024/day12.rs) | 526 |
8787

8888
## 2023
8989

src/year2024/day12.rs

Lines changed: 75 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,42 @@
11
//! # Garden Groups
2+
//!
3+
//! Part one flood fills each region, adding 4 to the perimeter for each plot
4+
//! then subtracting 2 for each neighbour that we've already added.
5+
//!
6+
//! Part two counts corners, as the number of corners equals the number of sides.
7+
//! We remove a corner when another plot is adjacent either up, down, left or right:
8+
//!
9+
//! ```none
10+
//! .#. ...
11+
//! .#. ##.
12+
//! ... ...
13+
//! ```
14+
//!
15+
//! We add back a corner when it's concave, for example where a plot is above, right but
16+
//! not above and to the right:
17+
//!
18+
//! ```none
19+
//! .#.
20+
//! .##
21+
//! ...
22+
//! ```
23+
//!
24+
//! There are 8 neighbours to check, giving 2⁸ possibilities. These are precomputed and cached
25+
//! in a lookup table.
226
use crate::util::grid::*;
3-
use crate::util::hash::*;
427
use crate::util::point::*;
28+
use std::array::from_fn;
529
use std::collections::VecDeque;
630

7-
const CLOCKWISE: [Point; 5] = [UP, RIGHT, DOWN, LEFT, UP];
8-
931
pub fn parse(input: &str) -> Grid<u8> {
1032
Grid::parse(input)
1133
}
1234

1335
pub fn part1(grid: &Grid<u8>) -> i32 {
36+
let mut result = 0;
1437
let mut todo = VecDeque::new();
1538
let mut seen = grid.same_size_with(false);
1639
let mut added = grid.same_size_with(false);
17-
let mut result = 0;
1840

1941
for y in 0..grid.height {
2042
for x in 0..grid.width {
@@ -23,44 +45,49 @@ pub fn part1(grid: &Grid<u8>) -> i32 {
2345
continue;
2446
}
2547

48+
// Flood fill each region.
2649
let kind = grid[point];
2750
let mut area = 0;
28-
let mut perm = 0;
51+
let mut perimeter = 0;
2952

3053
todo.push_back(point);
3154
seen[point] = true;
3255

3356
while let Some(point) = todo.pop_front() {
34-
area += 1;
35-
perm += 4;
3657
added[point] = true;
58+
area += 1;
59+
perimeter += 4;
3760

3861
for next in ORTHOGONAL.map(|o| point + o) {
3962
if grid.contains(next) && grid[next] == kind {
4063
if !seen[next] {
4164
seen[next] = true;
4265
todo.push_back(next);
4366
}
67+
// Remove both sides from neighbouring plots.
4468
if added[next] {
45-
perm -= 2;
69+
perimeter -= 2;
4670
}
4771
}
4872
}
4973
}
5074

51-
result += area * perm;
75+
result += area * perimeter;
5276
}
5377
}
5478

5579
result
5680
}
5781

58-
pub fn part2(grid: &Grid<u8>) -> u32 {
59-
let mut seen = grid.same_size_with(false);
60-
let mut todo = VecDeque::new();
61-
let mut corner = FastMap::new();
62-
let mut middle = FastMap::new();
82+
pub fn part2(grid: &Grid<u8>) -> usize {
83+
// Lookup table that returns number of corners for all combinations of neighbours.
84+
let lut = sides_lut();
85+
6386
let mut result = 0;
87+
let mut todo = VecDeque::new();
88+
let mut seen = grid.same_size_with(false);
89+
let mut added = grid.same_size_with(-1);
90+
let mut region = Vec::new();
6491

6592
for y in 0..grid.height {
6693
for x in 0..grid.width {
@@ -70,26 +97,14 @@ pub fn part2(grid: &Grid<u8>) -> u32 {
7097
}
7198

7299
let kind = grid[point];
73-
let mut size = 0;
74-
let mut sides = 0;
100+
let id = y * grid.width + x;
75101

76102
todo.push_back(point);
77103
seen[point] = true;
78104

79105
while let Some(point) = todo.pop_front() {
80-
size += 1;
81-
let x = 2 * point.x;
82-
let y = 2 * point.y;
83-
84-
*corner.entry(Point::new(x, y)).or_insert(0) += 1;
85-
*corner.entry(Point::new(x + 2, y)).or_insert(0) += 1;
86-
*corner.entry(Point::new(x, y + 2)).or_insert(0) += 1;
87-
*corner.entry(Point::new(x + 2, y + 2)).or_insert(0) += 1;
88-
89-
*middle.entry(Point::new(x + 1, y)).or_insert(0) += 1;
90-
*middle.entry(Point::new(x, y + 1)).or_insert(0) += 1;
91-
*middle.entry(Point::new(x + 2, y + 1)).or_insert(0) += 1;
92-
*middle.entry(Point::new(x + 1, y + 2)).or_insert(0) += 1;
106+
added[point] = id;
107+
region.push(point);
93108

94109
for next in ORTHOGONAL.map(|o| point + o) {
95110
if grid.contains(next) && grid[next] == kind && !seen[next] {
@@ -99,22 +114,41 @@ pub fn part2(grid: &Grid<u8>) -> u32 {
99114
}
100115
}
101116

102-
for (&point, _) in corner.iter().filter(|(_, &v)| v < 4) {
103-
let freq = CLOCKWISE.map(|c| *middle.get(&(point + c)).unwrap_or(&2));
104-
let count = freq.windows(2).filter(|w| w[0] < 2 && w[1] < 2).count();
117+
let size = region.len();
105118

106-
if count == 1 {
107-
sides += 1;
108-
} else if count == 4 {
109-
sides += 2;
110-
}
119+
for point in region.drain(..) {
120+
let index = DIAGONAL.iter().fold(0, |acc, &d| {
121+
(acc << 1) | (added.contains(point + d) && added[point + d] == id) as usize
122+
});
123+
result += size * lut[index];
111124
}
112-
113-
corner.clear();
114-
middle.clear();
115-
result += size * sides;
116125
}
117126
}
118127

119128
result
120129
}
130+
131+
/// There are 8 neighbours to check, giving 2⁸ possibilities. Precompute the number of corners
132+
/// once into a lookup table to speed things up.
133+
fn sides_lut() -> [usize; 256] {
134+
from_fn(|neighbours| {
135+
let [up_left, up, up_right, left, right, down_left, down, down_right] =
136+
from_fn(|i| neighbours & (1 << i) != 0);
137+
let mut sides = 0;
138+
139+
if !(up || left) || (up && left && !up_left) {
140+
sides += 1;
141+
}
142+
if !(up || right) || (up && right && !up_right) {
143+
sides += 1;
144+
}
145+
if !(down || left) || (down && left && !down_left) {
146+
sides += 1;
147+
}
148+
if !(down || right) || (down && right && !down_right) {
149+
sides += 1;
150+
}
151+
152+
sides
153+
})
154+
}

0 commit comments

Comments
 (0)