|
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