1
1
//! # Garden Groups
2
+ //!
3
+ //! Part one flood fills each region, adding 4 to the perimeter for each plot
4
+ //! then subtracting 2 for each neighbour that we've already added.
5
+ //!
6
+ //! Part two counts corners, as the number of corners equals the number of sides.
7
+ //! We remove a corner when another plot is adjacent either up, down, left or right:
8
+ //!
9
+ //! ```none
10
+ //! .#. ...
11
+ //! .#. ##.
12
+ //! ... ...
13
+ //! ```
14
+ //!
15
+ //! We add back a corner when it's concave, for example where a plot is above, right but
16
+ //! not above and to the right:
17
+ //!
18
+ //! ```none
19
+ //! .#.
20
+ //! .##
21
+ //! ...
22
+ //! ```
23
+ //!
24
+ //! There are 8 neighbours to check, giving 2⁸ possibilities. These are precomputed and cached
25
+ //! in a lookup table.
2
26
use crate :: util:: grid:: * ;
3
- use crate :: util:: hash:: * ;
4
27
use crate :: util:: point:: * ;
28
+ use std:: array:: from_fn;
5
29
use std:: collections:: VecDeque ;
6
30
7
- const CLOCKWISE : [ Point ; 5 ] = [ UP , RIGHT , DOWN , LEFT , UP ] ;
8
-
9
31
pub fn parse ( input : & str ) -> Grid < u8 > {
10
32
Grid :: parse ( input)
11
33
}
12
34
13
35
pub fn part1 ( grid : & Grid < u8 > ) -> i32 {
36
+ let mut result = 0 ;
14
37
let mut todo = VecDeque :: new ( ) ;
15
38
let mut seen = grid. same_size_with ( false ) ;
16
39
let mut added = grid. same_size_with ( false ) ;
17
- let mut result = 0 ;
18
40
19
41
for y in 0 ..grid. height {
20
42
for x in 0 ..grid. width {
@@ -23,44 +45,49 @@ pub fn part1(grid: &Grid<u8>) -> i32 {
23
45
continue ;
24
46
}
25
47
48
+ // Flood fill each region.
26
49
let kind = grid[ point] ;
27
50
let mut area = 0 ;
28
- let mut perm = 0 ;
51
+ let mut perimeter = 0 ;
29
52
30
53
todo. push_back ( point) ;
31
54
seen[ point] = true ;
32
55
33
56
while let Some ( point) = todo. pop_front ( ) {
34
- area += 1 ;
35
- perm += 4 ;
36
57
added[ point] = true ;
58
+ area += 1 ;
59
+ perimeter += 4 ;
37
60
38
61
for next in ORTHOGONAL . map ( |o| point + o) {
39
62
if grid. contains ( next) && grid[ next] == kind {
40
63
if !seen[ next] {
41
64
seen[ next] = true ;
42
65
todo. push_back ( next) ;
43
66
}
67
+ // Remove both sides from neighbouring plots.
44
68
if added[ next] {
45
- perm -= 2 ;
69
+ perimeter -= 2 ;
46
70
}
47
71
}
48
72
}
49
73
}
50
74
51
- result += area * perm ;
75
+ result += area * perimeter ;
52
76
}
53
77
}
54
78
55
79
result
56
80
}
57
81
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 ( ) ;
82
+ pub fn part2 ( grid : & Grid < u8 > ) -> usize {
83
+ // Lookup table that returns number of corners for all combinations of neighbours.
84
+ let lut = sides_lut ( ) ;
85
+
63
86
let mut result = 0 ;
87
+ let mut todo = VecDeque :: new ( ) ;
88
+ let mut seen = grid. same_size_with ( false ) ;
89
+ let mut added = grid. same_size_with ( -1 ) ;
90
+ let mut region = Vec :: new ( ) ;
64
91
65
92
for y in 0 ..grid. height {
66
93
for x in 0 ..grid. width {
@@ -70,26 +97,15 @@ pub fn part2(grid: &Grid<u8>) -> u32 {
70
97
}
71
98
72
99
let kind = grid[ point] ;
73
- let mut size = 0 ;
100
+ let id = y * grid . width + x ;
74
101
let mut sides = 0 ;
75
102
76
103
todo. push_back ( point) ;
77
104
seen[ point] = true ;
78
105
79
106
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 ;
107
+ added[ point] = id;
108
+ region. push ( point) ;
93
109
94
110
for next in ORTHOGONAL . map ( |o| point + o) {
95
111
if grid. contains ( next) && grid[ next] == kind && !seen[ next] {
@@ -99,22 +115,42 @@ pub fn part2(grid: &Grid<u8>) -> u32 {
99
115
}
100
116
}
101
117
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 ( ) ;
105
-
106
- if count == 1 {
107
- sides += 1 ;
108
- } else if count == 4 {
109
- sides += 2 ;
110
- }
118
+ for & point in & region {
119
+ let index = DIAGONAL . iter ( ) . fold ( 0 , |acc, & d| {
120
+ ( acc << 1 ) | ( added. contains ( point + d) && added[ point + d] == id) as usize
121
+ } ) ;
122
+ sides += lut[ index] ;
111
123
}
112
124
113
- corner. clear ( ) ;
114
- middle. clear ( ) ;
115
- result += size * sides;
125
+ result += region. len ( ) * sides;
126
+ region. clear ( ) ;
116
127
}
117
128
}
118
129
119
130
result
120
131
}
132
+
133
+ /// There are 8 neighbours to check, giving 2⁸ possibilities. Precompute the number of corners
134
+ /// once into a lookup table to speed things up.
135
+ fn sides_lut ( ) -> [ usize ; 256 ] {
136
+ from_fn ( |neighbours| {
137
+ let [ up_left, up, up_right, left, right, down_left, down, down_right] =
138
+ from_fn ( |i| neighbours & ( 1 << i) != 0 ) ;
139
+ let mut sides = 0 ;
140
+
141
+ if !( up || left) || ( up && left && !up_left) {
142
+ sides += 1 ;
143
+ }
144
+ if !( up || right) || ( up && right && !up_right) {
145
+ sides += 1 ;
146
+ }
147
+ if !( down || left) || ( down && left && !down_left) {
148
+ sides += 1 ;
149
+ }
150
+ if !( down || right) || ( down && right && !down_right) {
151
+ sides += 1 ;
152
+ }
153
+
154
+ sides
155
+ } )
156
+ }
0 commit comments