Skip to content

Commit 9543089

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

File tree

2 files changed

+85
-85
lines changed

2 files changed

+85
-85
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) | 310 |
8787

8888
## 2023
8989

src/year2024/day12.rs

Lines changed: 84 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,120 @@
11
//! # Garden Groups
2+
//!
3+
//! Solves both parts simultaneously by flood filling each region.
4+
//!
5+
//! For part one we increment the perimeter for each neighbouring plot out of bounds
6+
//! or a different kind of plant.
7+
//!
8+
//! For part two we count corners, as the number of corners equals the number of sides.
9+
//! We remove a corner when another plot is adjacent either up, down, left or right:
10+
//!
11+
//! ```none
12+
//! .#. ...
13+
//! .#. ##.
14+
//! ... ...
15+
//! ```
16+
//!
17+
//! We add back a corner when it's concave, for example where a plot is above, right but
18+
//! not above and to the right:
19+
//!
20+
//! ```none
21+
//! .#.
22+
//! .##
23+
//! ...
24+
//! ```
25+
//!
26+
//! There are 8 neighbours to check, giving 2⁸ possibilities. To speed things up these are
27+
//! precomputed and cached in a lookup table.
228
use crate::util::grid::*;
3-
use crate::util::hash::*;
429
use crate::util::point::*;
5-
use std::collections::VecDeque;
30+
use std::array::from_fn;
631

7-
const CLOCKWISE: [Point; 5] = [UP, RIGHT, DOWN, LEFT, UP];
32+
type Input = (usize, usize);
833

9-
pub fn parse(input: &str) -> Grid<u8> {
10-
Grid::parse(input)
11-
}
34+
pub fn parse(input: &str) -> Input {
35+
let grid = Grid::parse(input);
36+
let lut = sides_lut();
37+
38+
let mut region = Vec::new();
39+
let mut seen = grid.same_size_with(-1);
1240

13-
pub fn part1(grid: &Grid<u8>) -> i32 {
14-
let mut todo = VecDeque::new();
15-
let mut seen = grid.same_size_with(false);
16-
let mut added = grid.same_size_with(false);
17-
let mut result = 0;
41+
let mut part_one = 0;
42+
let mut part_two = 0;
1843

1944
for y in 0..grid.height {
2045
for x in 0..grid.width {
46+
// Skip already filled points.
2147
let point = Point::new(x, y);
22-
if seen[point] {
48+
if seen[point] != -1 {
2349
continue;
2450
}
2551

52+
// Assign a unique id to each region based on the first point that we encounter.
2653
let kind = grid[point];
54+
let id = y * grid.width + x;
55+
56+
// Flood fill.
2757
let mut area = 0;
28-
let mut perm = 0;
58+
let mut perimeter = 0;
2959

30-
todo.push_back(point);
31-
seen[point] = true;
60+
region.push(point);
61+
seen[point] = id;
3262

33-
while let Some(point) = todo.pop_front() {
63+
while area < region.len() {
64+
let point = region[area];
3465
area += 1;
35-
perm += 4;
36-
added[point] = true;
3766

3867
for next in ORTHOGONAL.map(|o| point + o) {
3968
if grid.contains(next) && grid[next] == kind {
40-
if !seen[next] {
41-
seen[next] = true;
42-
todo.push_back(next);
43-
}
44-
if added[next] {
45-
perm -= 2;
69+
if seen[next] == -1 {
70+
region.push(next);
71+
seen[next] = id;
4672
}
73+
} else {
74+
perimeter += 1;
4775
}
4876
}
4977
}
5078

51-
result += area * perm;
79+
// Sum sides for all plots in the region.
80+
let sides: usize = region
81+
.drain(..)
82+
.map(|point| {
83+
let index = DIAGONAL.iter().fold(0, |acc, &d| {
84+
(acc << 1) | (seen.contains(point + d) && seen[point + d] == id) as usize
85+
});
86+
lut[index]
87+
})
88+
.sum();
89+
90+
part_one += area * perimeter;
91+
part_two += area * sides;
5292
}
5393
}
5494

55-
result
95+
(part_one, part_two)
5696
}
5797

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();
63-
let mut result = 0;
64-
65-
for y in 0..grid.height {
66-
for x in 0..grid.width {
67-
let point = Point::new(x, y);
68-
if seen[point] {
69-
continue;
70-
}
71-
72-
let kind = grid[point];
73-
let mut size = 0;
74-
let mut sides = 0;
75-
76-
todo.push_back(point);
77-
seen[point] = true;
78-
79-
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;
93-
94-
for next in ORTHOGONAL.map(|o| point + o) {
95-
if grid.contains(next) && grid[next] == kind && !seen[next] {
96-
seen[next] = true;
97-
todo.push_back(next);
98-
}
99-
}
100-
}
98+
pub fn part1(input: &Input) -> usize {
99+
input.0
100+
}
101101

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();
102+
pub fn part2(input: &Input) -> usize {
103+
input.1
104+
}
105105

106-
if count == 1 {
107-
sides += 1;
108-
} else if count == 4 {
109-
sides += 2;
110-
}
111-
}
106+
/// There are 8 neighbours to check, giving 2⁸ possibilities.
107+
/// Precompute the number of corners once into a lookup table to speed things up.
108+
fn sides_lut() -> [usize; 256] {
109+
from_fn(|neighbours| {
110+
let [up_left, up, up_right, left, right, down_left, down, down_right] =
111+
from_fn(|i| neighbours & (1 << i) != 0);
112112

113-
corner.clear();
114-
middle.clear();
115-
result += size * sides;
116-
}
117-
}
113+
let ul = !(up || left) || (up && left && !up_left);
114+
let ur = !(up || right) || (up && right && !up_right);
115+
let dl = !(down || left) || (down && left && !down_left);
116+
let dr = !(down || right) || (down && right && !down_right);
118117

119-
result
118+
(ul as usize) + (ur as usize) + (dl as usize) + (dr as usize)
119+
})
120120
}

0 commit comments

Comments
 (0)