|
1 | 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 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. |
2 | 28 | use crate::util::grid::*;
|
3 |
| -use crate::util::hash::*; |
4 | 29 | use crate::util::point::*;
|
| 30 | +use std::array::from_fn; |
5 | 31 | use std::collections::VecDeque;
|
6 | 32 |
|
7 |
| -const CLOCKWISE: [Point; 5] = [UP, RIGHT, DOWN, LEFT, UP]; |
| 33 | +type Input = (usize, usize); |
8 | 34 |
|
9 |
| -pub fn parse(input: &str) -> Grid<u8> { |
10 |
| - Grid::parse(input) |
11 |
| -} |
| 35 | +pub fn parse(input: &str) -> Input { |
| 36 | + let grid = Grid::parse(input); |
| 37 | + let lut = sides_lut(); |
12 | 38 |
|
13 |
| -pub fn part1(grid: &Grid<u8>) -> i32 { |
14 | 39 | 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; |
| 40 | + let mut seen = grid.same_size_with(-1); |
| 41 | + let mut region = Vec::new(); |
| 42 | + let mut part_one = 0; |
| 43 | + let mut part_two = 0; |
18 | 44 |
|
19 | 45 | for y in 0..grid.height {
|
20 | 46 | for x in 0..grid.width {
|
| 47 | + // Skip already filled points. |
21 | 48 | let point = Point::new(x, y);
|
22 |
| - if seen[point] { |
| 49 | + if seen[point] != -1 { |
23 | 50 | continue;
|
24 | 51 | }
|
25 | 52 |
|
| 53 | + // Assign a unique id to each region, based on the first point that we encounter. |
26 | 54 | let kind = grid[point];
|
27 |
| - let mut area = 0; |
28 |
| - let mut perm = 0; |
| 55 | + let id = y * grid.width + x; |
| 56 | + let mut perimeter = 0; |
29 | 57 |
|
| 58 | + // Flood fill. |
30 | 59 | todo.push_back(point);
|
31 |
| - seen[point] = true; |
| 60 | + seen[point] = id; |
32 | 61 |
|
33 | 62 | while let Some(point) = todo.pop_front() {
|
34 |
| - area += 1; |
35 |
| - perm += 4; |
36 |
| - added[point] = true; |
| 63 | + region.push(point); |
37 | 64 |
|
38 | 65 | for next in ORTHOGONAL.map(|o| point + o) {
|
39 | 66 | if grid.contains(next) && grid[next] == kind {
|
40 |
| - if !seen[next] { |
41 |
| - seen[next] = true; |
| 67 | + if seen[next] == -1 { |
| 68 | + seen[next] = id; |
42 | 69 | todo.push_back(next);
|
43 | 70 | }
|
44 |
| - if added[next] { |
45 |
| - perm -= 2; |
46 |
| - } |
| 71 | + } else { |
| 72 | + perimeter += 1; |
47 | 73 | }
|
48 | 74 | }
|
49 | 75 | }
|
50 | 76 |
|
51 |
| - result += area * perm; |
| 77 | + // Sum sides for all plots in the region. |
| 78 | + let area = region.len(); |
| 79 | + let sides: usize = region |
| 80 | + .drain(..) |
| 81 | + .map(|point| { |
| 82 | + let index = DIAGONAL.iter().fold(0, |acc, &d| { |
| 83 | + (acc << 1) | (seen.contains(point + d) && seen[point + d] == id) as usize |
| 84 | + }); |
| 85 | + lut[index] |
| 86 | + }) |
| 87 | + .sum(); |
| 88 | + |
| 89 | + part_one += area * perimeter; |
| 90 | + part_two += area * sides; |
52 | 91 | }
|
53 | 92 | }
|
54 | 93 |
|
55 |
| - result |
| 94 | + (part_one, part_two) |
56 | 95 | }
|
57 | 96 |
|
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 |
| - } |
| 97 | +pub fn part1(input: &Input) -> usize { |
| 98 | + input.0 |
| 99 | +} |
101 | 100 |
|
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(); |
| 101 | +pub fn part2(input: &Input) -> usize { |
| 102 | + input.1 |
| 103 | +} |
105 | 104 |
|
106 |
| - if count == 1 { |
107 |
| - sides += 1; |
108 |
| - } else if count == 4 { |
109 |
| - sides += 2; |
110 |
| - } |
111 |
| - } |
| 105 | +/// There are 8 neighbours to check, giving 2⁸ possibilities. |
| 106 | +/// Precompute the number of corners once into a lookup table to speed things up. |
| 107 | +fn sides_lut() -> [usize; 256] { |
| 108 | + from_fn(|neighbours| { |
| 109 | + let [up_left, up, up_right, left, right, down_left, down, down_right] = |
| 110 | + from_fn(|i| neighbours & (1 << i) != 0); |
112 | 111 |
|
113 |
| - corner.clear(); |
114 |
| - middle.clear(); |
115 |
| - result += size * sides; |
116 |
| - } |
117 |
| - } |
| 112 | + let ul = !(up || left) || (up && left && !up_left); |
| 113 | + let ur = !(up || right) || (up && right && !up_right); |
| 114 | + let dl = !(down || left) || (down && left && !down_left); |
| 115 | + let dr = !(down || right) || (down && right && !down_right); |
118 | 116 |
|
119 |
| - result |
| 117 | + (ul as usize) + (ur as usize) + (dl as usize) + (dr as usize) |
| 118 | + }) |
120 | 119 | }
|
0 commit comments