Skip to content

Commit cdc8222

Browse files
committed
Faster simpler approach
1 parent 6eaba1c commit cdc8222

File tree

2 files changed

+75
-48
lines changed

2 files changed

+75
-48
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: 74 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,40 @@
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 {
1436
let mut todo = VecDeque::new();
1537
let mut seen = grid.same_size_with(false);
16-
let mut added = grid.same_size_with(false);
1738
let mut result = 0;
1839

1940
for y in 0..grid.height {
@@ -23,98 +44,104 @@ pub fn part1(grid: &Grid<u8>) -> i32 {
2344
continue;
2445
}
2546

47+
// Flood fill each region.
2648
let kind = grid[point];
2749
let mut area = 0;
28-
let mut perm = 0;
50+
let mut perimeter = 0;
2951

3052
todo.push_back(point);
3153
seen[point] = true;
3254

3355
while let Some(point) = todo.pop_front() {
3456
area += 1;
35-
perm += 4;
36-
added[point] = true;
3757

3858
for next in ORTHOGONAL.map(|o| point + o) {
3959
if grid.contains(next) && grid[next] == kind {
4060
if !seen[next] {
4161
seen[next] = true;
4262
todo.push_back(next);
4363
}
44-
if added[next] {
45-
perm -= 2;
46-
}
64+
} else {
65+
perimeter += 1;
4766
}
4867
}
4968
}
5069

51-
result += area * perm;
70+
result += area * perimeter;
5271
}
5372
}
5473

5574
result
5675
}
5776

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();
77+
pub fn part2(grid: &Grid<u8>) -> usize {
78+
// Lookup table that returns number of corners for all combinations of neighbours.
79+
let lut = sides_lut();
80+
6381
let mut result = 0;
82+
let mut todo = VecDeque::new();
83+
let mut seen = grid.same_size_with(-1);
84+
let mut region = Vec::new();
6485

6586
for y in 0..grid.height {
6687
for x in 0..grid.width {
6788
let point = Point::new(x, y);
68-
if seen[point] {
89+
if seen[point] != -1 {
6990
continue;
7091
}
7192

7293
let kind = grid[point];
73-
let mut size = 0;
74-
let mut sides = 0;
94+
let id = y * grid.width + x;
7595

7696
todo.push_back(point);
77-
seen[point] = true;
97+
seen[point] = id;
7898

7999
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;
100+
region.push(point);
93101

94102
for next in ORTHOGONAL.map(|o| point + o) {
95-
if grid.contains(next) && grid[next] == kind && !seen[next] {
96-
seen[next] = true;
103+
if grid.contains(next) && grid[next] == kind && seen[next] == -1 {
104+
seen[next] = id;
97105
todo.push_back(next);
98106
}
99107
}
100108
}
101109

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();
110+
let size = region.len();
105111

106-
if count == 1 {
107-
sides += 1;
108-
} else if count == 4 {
109-
sides += 2;
110-
}
112+
for point in region.drain(..) {
113+
let index = DIAGONAL.iter().fold(0, |acc, &d| {
114+
(acc << 1) | (seen.contains(point + d) && seen[point + d] == id) as usize
115+
});
116+
result += size * lut[index];
111117
}
112-
113-
corner.clear();
114-
middle.clear();
115-
result += size * sides;
116118
}
117119
}
118120

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

0 commit comments

Comments
 (0)