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 {
14
36
let mut todo = VecDeque :: new ( ) ;
15
37
let mut seen = grid. same_size_with ( false ) ;
16
- let mut added = grid. same_size_with ( false ) ;
17
38
let mut result = 0 ;
18
39
19
40
for y in 0 ..grid. height {
@@ -23,98 +44,104 @@ pub fn part1(grid: &Grid<u8>) -> i32 {
23
44
continue ;
24
45
}
25
46
47
+ // Flood fill each region.
26
48
let kind = grid[ point] ;
27
49
let mut area = 0 ;
28
- let mut perm = 0 ;
50
+ let mut perimeter = 0 ;
29
51
30
52
todo. push_back ( point) ;
31
53
seen[ point] = true ;
32
54
33
55
while let Some ( point) = todo. pop_front ( ) {
34
56
area += 1 ;
35
- perm += 4 ;
36
- added[ point] = true ;
37
57
38
58
for next in ORTHOGONAL . map ( |o| point + o) {
39
59
if grid. contains ( next) && grid[ next] == kind {
40
60
if !seen[ next] {
41
61
seen[ next] = true ;
42
62
todo. push_back ( next) ;
43
63
}
44
- if added[ next] {
45
- perm -= 2 ;
46
- }
64
+ } else {
65
+ perimeter += 1 ;
47
66
}
48
67
}
49
68
}
50
69
51
- result += area * perm ;
70
+ result += area * perimeter ;
52
71
}
53
72
}
54
73
55
74
result
56
75
}
57
76
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 ( ) ;
77
+ pub fn part2 ( grid : & Grid < u8 > ) -> usize {
78
+ // Lookup table that returns number of corners for all combinations of neighbours.
79
+ let lut = sides_lut ( ) ;
80
+
63
81
let mut result = 0 ;
82
+ let mut todo = VecDeque :: new ( ) ;
83
+ let mut seen = grid. same_size_with ( -1 ) ;
84
+ let mut region = Vec :: new ( ) ;
64
85
65
86
for y in 0 ..grid. height {
66
87
for x in 0 ..grid. width {
67
88
let point = Point :: new ( x, y) ;
68
- if seen[ point] {
89
+ if seen[ point] != - 1 {
69
90
continue ;
70
91
}
71
92
72
93
let kind = grid[ point] ;
73
- let mut size = 0 ;
74
- let mut sides = 0 ;
94
+ let id = y * grid. width + x;
75
95
76
96
todo. push_back ( point) ;
77
- seen[ point] = true ;
97
+ seen[ point] = id ;
78
98
79
99
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 ;
100
+ region. push ( point) ;
93
101
94
102
for next in ORTHOGONAL . map ( |o| point + o) {
95
- if grid. contains ( next) && grid[ next] == kind && ! seen[ next] {
96
- seen[ next] = true ;
103
+ if grid. contains ( next) && grid[ next] == kind && seen[ next] == - 1 {
104
+ seen[ next] = id ;
97
105
todo. push_back ( next) ;
98
106
}
99
107
}
100
108
}
101
109
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 ( ) ;
110
+ let size = region. len ( ) ;
105
111
106
- if count == 1 {
107
- sides += 1 ;
108
- } else if count == 4 {
109
- sides += 2 ;
110
- }
112
+ for point in region . drain ( .. ) {
113
+ let index = DIAGONAL . iter ( ) . fold ( 0 , |acc , & d| {
114
+ ( acc << 1 ) | ( seen . contains ( point + d ) && seen [ point + d ] == id ) as usize
115
+ } ) ;
116
+ result += size * lut [ index ] ;
111
117
}
112
-
113
- corner. clear ( ) ;
114
- middle. clear ( ) ;
115
- result += size * sides;
116
118
}
117
119
}
118
120
119
121
result
120
122
}
123
+
124
+ /// There are 8 neighbours to check, giving 2⁸ possibilities. Precompute the number of corners
125
+ /// once into a lookup table to speed things up.
126
+ fn sides_lut ( ) -> [ usize ; 256 ] {
127
+ from_fn ( |neighbours| {
128
+ let [ up_left, up, up_right, left, right, down_left, down, down_right] =
129
+ from_fn ( |i| neighbours & ( 1 << i) != 0 ) ;
130
+ let mut sides = 0 ;
131
+
132
+ if !( up || left) || ( up && left && !up_left) {
133
+ sides += 1 ;
134
+ }
135
+ if !( up || right) || ( up && right && !up_right) {
136
+ sides += 1 ;
137
+ }
138
+ if !( down || left) || ( down && left && !down_left) {
139
+ sides += 1 ;
140
+ }
141
+ if !( down || right) || ( down && right && !down_right) {
142
+ sides += 1 ;
143
+ }
144
+
145
+ sides
146
+ } )
147
+ }
0 commit comments