Skip to content

Commit 6e20776

Browse files
committed
Simpler approach
1 parent 6627bc7 commit 6e20776

File tree

2 files changed

+38
-57
lines changed

2 files changed

+38
-57
lines changed

README.md

+1-1
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) | 310 |
86+
| 12 | [Garden Groups](https://adventofcode.com/2024/day/12) | [Source](src/year2024/day12.rs) | 289 |
8787

8888
## 2023
8989

src/year2024/day12.rs

+37-56
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,31 @@
22
//!
33
//! Solves both parts simultaneously by flood filling each region.
44
//!
5-
//! For part one we increment the perimeter for each neighbouring plot out of bounds
6-
//! or a different kind of plant.
5+
//! For part one we increment the perimeter for each neighbouring plot belonging to a different
6+
//! region or out of bounds.
77
//!
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-
//! For example, considering the top left corner of the plot:
8+
//! For part two we count each plot on the edge as either 0, 1 or 2 sides then divide by 2.
9+
//! An edge plot contributes nothing if it has 2 edge neighbours facing the same way,
10+
//! one if has a single neighbour and two if it has no neighbours.
1111
//!
12-
//! ```none
13-
//! .. .# .. ##
14-
//! .# ✓ .# ✗ ## ✗ ## ✗
15-
//! ```
16-
//!
17-
//! However we add back a corner when it's concave, for example where a plot is above, left but
18-
//! not above and to the left:
12+
//! For example, considering the right edge:
1913
//!
2014
//! ```none
21-
//! .#
22-
//! ## ✓
15+
//! ... ... .#. > 1
16+
//! .#. > 2 .#. > 1 .#. > 0
17+
//! ... .#. > 1 .#. > 1
2318
//! ```
24-
//!
25-
//! There are 8 neighbours to check, giving 2⁸ possibilities. To speed things up these are
26-
//! precomputed and cached in a lookup table.
2719
use crate::util::grid::*;
2820
use crate::util::point::*;
29-
use std::array::from_fn;
3021

3122
type Input = (usize, usize);
3223

3324
pub fn parse(input: &str) -> Input {
3425
let grid = Grid::parse(input);
35-
let lut = sides_lut();
3626

37-
let mut region = Vec::new();
38-
let mut seen = grid.same_size_with(0);
27+
let mut todo = Vec::new();
28+
let mut edge = Vec::new();
29+
let mut seen = grid.same_size_with(false);
3930

4031
let mut part_one = 0;
4132
let mut part_two = 0;
@@ -44,48 +35,54 @@ pub fn parse(input: &str) -> Input {
4435
for x in 0..grid.width {
4536
// Skip already filled points.
4637
let point = Point::new(x, y);
47-
if seen[point] > 0 {
38+
if seen[point] {
4839
continue;
4940
}
5041

51-
// Assign a unique id to each region based on the first point that we encounter.
42+
// Flood fill, using area as an index.
5243
let kind = grid[point];
53-
let id = y * grid.width + x + 1;
44+
let check = |point| grid.contains(point) && grid[point] == kind;
5445

55-
// Flood fill, using area as an index.
5646
let mut area = 0;
5747
let mut perimeter = 0;
5848
let mut sides = 0;
5949

60-
region.push(point);
61-
seen[point] = id;
50+
todo.push(point);
51+
seen[point] = true;
6252

63-
while area < region.len() {
64-
let point = region[area];
53+
while area < todo.len() {
54+
let point = todo[area];
6555
area += 1;
6656

67-
for next in ORTHOGONAL.map(|o| point + o) {
68-
if grid.contains(next) && grid[next] == kind {
69-
if seen[next] == 0 {
70-
region.push(next);
71-
seen[next] = id;
57+
for direction in ORTHOGONAL {
58+
let next = point + direction;
59+
60+
if check(next) {
61+
if !seen[next] {
62+
todo.push(next);
63+
seen[next] = true;
7264
}
7365
} else {
66+
edge.push((point, direction));
7467
perimeter += 1;
7568
}
7669
}
7770
}
7871

7972
// Sum sides for all plots in the region.
80-
for point in region.drain(..) {
81-
let index = DIAGONAL.iter().fold(0, |acc, &d| {
82-
(acc << 1) | (seen.contains(point + d) && seen[point + d] == id) as usize
83-
});
84-
sides += lut[index];
73+
for &(p, d) in &edge {
74+
let r = d.clockwise();
75+
let l = d.counter_clockwise();
76+
77+
sides += (!check(p + l) || check(p + l + d)) as usize;
78+
sides += (!check(p + r) || check(p + r + d)) as usize;
8579
}
8680

81+
todo.clear();
82+
edge.clear();
83+
8784
part_one += area * perimeter;
88-
part_two += area * sides;
85+
part_two += area * (sides / 2);
8986
}
9087
}
9188

@@ -99,19 +96,3 @@ pub fn part1(input: &Input) -> usize {
9996
pub fn part2(input: &Input) -> usize {
10097
input.1
10198
}
102-
103-
/// There are 8 neighbours to check, giving 2⁸ possibilities.
104-
/// Precompute the number of corners once into a lookup table to speed things up.
105-
fn sides_lut() -> [usize; 256] {
106-
from_fn(|neighbours| {
107-
let [up_left, up, up_right, left, right, down_left, down, down_right] =
108-
from_fn(|i| neighbours & (1 << i) > 0);
109-
110-
let ul = !(up || left) || (up && left && !up_left);
111-
let ur = !(up || right) || (up && right && !up_right);
112-
let dl = !(down || left) || (down && left && !down_left);
113-
let dr = !(down || right) || (down && right && !down_right);
114-
115-
(ul as usize) + (ur as usize) + (dl as usize) + (dr as usize)
116-
})
117-
}

0 commit comments

Comments
 (0)