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