Skip to main content

aoc/year2024/
day12.rs

1//! # Garden Groups
2//!
3//! Solves both parts simultaneously by flood filling each region.
4//!
5//! For part one we increment the perimeter for each neighboring plot belonging to a different
6//! region or out of bounds.
7//!
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 neighbors facing the same way,
10//! one if it has a single neighbor and two if it has no neighbors.
11//!
12//! For example, considering the right edge:
13//!
14//! ```none
15//! ...        ...        .#. > 1
16//! .#. > 2    .#. > 1    .#. > 0
17//! ...        .#. > 1    .#. > 1
18//! ```
19use crate::util::grid::*;
20use crate::util::point::*;
21
22type Input = (usize, usize);
23
24pub fn parse(input: &str) -> Input {
25    // A newline border allows us to avoid boundary checks.
26    let grid = Grid::parse_with_border(input);
27
28    let mut todo = Vec::new();
29    let mut edge = Vec::new();
30    let mut seen = grid.same_size_with(false);
31
32    let mut part_one = 0;
33    let mut part_two = 0;
34
35    // Iterate over every point, skipping the border.
36    for y in 1..grid.height - 1 {
37        for x in 1..grid.width {
38            // Skip already filled points.
39            let point = Point::new(x, y);
40            if seen[point] {
41                continue;
42            }
43
44            // Flood fill, using area as an index.
45            let kind = grid[point];
46            let check = |point| grid[point] == kind;
47
48            let mut area = 0;
49            let mut perimeter = 0;
50
51            todo.push(point);
52            seen[point] = true;
53
54            while area < todo.len() {
55                let point = todo[area];
56                area += 1;
57
58                for direction in ORTHOGONAL {
59                    let next = point + direction;
60
61                    if check(next) {
62                        if !seen[next] {
63                            todo.push(next);
64                            seen[next] = true;
65                        }
66                    } else {
67                        edge.push((point, direction));
68                        perimeter += 1;
69                    }
70                }
71            }
72
73            // Sum sides for all plots in the region.
74            let mut sides = 0;
75
76            for &(p, d) in &edge {
77                for t in [d.clockwise(), d.counter_clockwise()] {
78                    sides += usize::from(!check(p + t) || check(p + t + d));
79                }
80            }
81
82            todo.clear();
83            edge.clear();
84
85            part_one += area * perimeter;
86            part_two += area * (sides / 2);
87        }
88    }
89
90    (part_one, part_two)
91}
92
93pub fn part1(input: &Input) -> usize {
94    input.0
95}
96
97pub fn part2(input: &Input) -> usize {
98    input.1
99}