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 neighbouring 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 neighbours facing the same way,
10//! one if has a single neighbour and two if it has no neighbours.
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    let grid = Grid::parse(input);
26
27    let mut todo = Vec::new();
28    let mut edge = Vec::new();
29    let mut seen = grid.same_size_with(false);
30
31    let mut part_one = 0;
32    let mut part_two = 0;
33
34    for y in 0..grid.height {
35        for x in 0..grid.width {
36            // Skip already filled points.
37            let point = Point::new(x, y);
38            if seen[point] {
39                continue;
40            }
41
42            // Flood fill, using area as an index.
43            let kind = grid[point];
44            let check = |point| grid.contains(point) && grid[point] == kind;
45
46            let mut area = 0;
47            let mut perimeter = 0;
48            let mut sides = 0;
49
50            todo.push(point);
51            seen[point] = true;
52
53            while area < todo.len() {
54                let point = todo[area];
55                area += 1;
56
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;
64                        }
65                    } else {
66                        edge.push((point, direction));
67                        perimeter += 1;
68                    }
69                }
70            }
71
72            // Sum sides for all plots in the region.
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;
79            }
80
81            todo.clear();
82            edge.clear();
83
84            part_one += area * perimeter;
85            part_two += area * (sides / 2);
86        }
87    }
88
89    (part_one, part_two)
90}
91
92pub fn part1(input: &Input) -> usize {
93    input.0
94}
95
96pub fn part2(input: &Input) -> usize {
97    input.1
98}