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,14 @@ pub fn part2(grid: &Grid<u8>) -> u32 {
70
97
}
71
98
72
99
let kind = grid[ point] ;
73
- let mut size = 0 ;
74
- let mut sides = 0 ;
100
+ let id = y * grid. width + x;
75
101
76
102
todo. push_back ( point) ;
77
103
seen[ point] = true ;
78
104
79
105
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 ;
106
+ added[ point] = id;
107
+ region. push ( point) ;
93
108
94
109
for next in ORTHOGONAL . map ( |o| point + o) {
95
110
if grid. contains ( next) && grid[ next] == kind && !seen[ next] {
@@ -99,22 +114,41 @@ pub fn part2(grid: &Grid<u8>) -> u32 {
99
114
}
100
115
}
101
116
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 ( ) ;
117
+ let size = region. len ( ) ;
105
118
106
- if count == 1 {
107
- sides += 1 ;
108
- } else if count == 4 {
109
- sides += 2 ;
110
- }
119
+ for point in region . drain ( .. ) {
120
+ let index = DIAGONAL . iter ( ) . fold ( 0 , |acc , & d| {
121
+ ( acc << 1 ) | ( added . contains ( point + d ) && added [ point + d ] == id ) as usize
122
+ } ) ;
123
+ result += size * lut [ index ] ;
111
124
}
112
-
113
- corner. clear ( ) ;
114
- middle. clear ( ) ;
115
- result += size * sides;
116
125
}
117
126
}
118
127
119
128
result
120
129
}
130
+
131
+ /// There are 8 neighbours to check, giving 2⁸ possibilities. Precompute the number of corners
132
+ /// once into a lookup table to speed things up.
133
+ fn sides_lut ( ) -> [ usize ; 256 ] {
134
+ from_fn ( |neighbours| {
135
+ let [ up_left, up, up_right, left, right, down_left, down, down_right] =
136
+ from_fn ( |i| neighbours & ( 1 << i) != 0 ) ;
137
+ let mut sides = 0 ;
138
+
139
+ if !( up || left) || ( up && left && !up_left) {
140
+ sides += 1 ;
141
+ }
142
+ if !( up || right) || ( up && right && !up_right) {
143
+ sides += 1 ;
144
+ }
145
+ if !( down || left) || ( down && left && !down_left) {
146
+ sides += 1 ;
147
+ }
148
+ if !( down || right) || ( down && right && !down_right) {
149
+ sides += 1 ;
150
+ }
151
+
152
+ sides
153
+ } )
154
+ }
0 commit comments