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