11//! # 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.
226use crate :: util:: grid:: * ;
3- use crate :: util:: hash:: * ;
427use crate :: util:: point:: * ;
28+ use std:: array:: from_fn;
529use std:: collections:: VecDeque ;
630
7- const CLOCKWISE : [ Point ; 5 ] = [ UP , RIGHT , DOWN , LEFT , UP ] ;
8-
931pub fn parse ( input : & str ) -> Grid < u8 > {
1032 Grid :: parse ( input)
1133}
1234
1335pub fn part1 ( grid : & Grid < u8 > ) -> i32 {
36+ let mut result = 0 ;
1437 let mut todo = VecDeque :: new ( ) ;
1538 let mut seen = grid. same_size_with ( false ) ;
1639 let mut added = grid. same_size_with ( false ) ;
17- let mut result = 0 ;
1840
1941 for y in 0 ..grid. height {
2042 for x in 0 ..grid. width {
@@ -23,44 +45,49 @@ pub fn part1(grid: &Grid<u8>) -> i32 {
2345 continue ;
2446 }
2547
48+ // Flood fill each region.
2649 let kind = grid[ point] ;
2750 let mut area = 0 ;
28- let mut perm = 0 ;
51+ let mut perimeter = 0 ;
2952
3053 todo. push_back ( point) ;
3154 seen[ point] = true ;
3255
3356 while let Some ( point) = todo. pop_front ( ) {
34- area += 1 ;
35- perm += 4 ;
3657 added[ point] = true ;
58+ area += 1 ;
59+ perimeter += 4 ;
3760
3861 for next in ORTHOGONAL . map ( |o| point + o) {
3962 if grid. contains ( next) && grid[ next] == kind {
4063 if !seen[ next] {
4164 seen[ next] = true ;
4265 todo. push_back ( next) ;
4366 }
67+ // Remove both sides from neighbouring plots.
4468 if added[ next] {
45- perm -= 2 ;
69+ perimeter -= 2 ;
4670 }
4771 }
4872 }
4973 }
5074
51- result += area * perm ;
75+ result += area * perimeter ;
5276 }
5377 }
5478
5579 result
5680}
5781
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+
6386 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 ( ) ;
6491
6592 for y in 0 ..grid. height {
6693 for x in 0 ..grid. width {
@@ -70,26 +97,15 @@ pub fn part2(grid: &Grid<u8>) -> u32 {
7097 }
7198
7299 let kind = grid[ point] ;
73- let mut size = 0 ;
100+ let id = y * grid . width + x ;
74101 let mut sides = 0 ;
75102
76103 todo. push_back ( point) ;
77104 seen[ point] = true ;
78105
79106 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) ;
93109
94110 for next in ORTHOGONAL . map ( |o| point + o) {
95111 if grid. contains ( next) && grid[ next] == kind && !seen[ next] {
@@ -99,22 +115,42 @@ pub fn part2(grid: &Grid<u8>) -> u32 {
99115 }
100116 }
101117
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] ;
111123 }
112124
113- corner. clear ( ) ;
114- middle. clear ( ) ;
115- result += size * sides;
125+ result += region. len ( ) * sides;
126+ region. clear ( ) ;
116127 }
117128 }
118129
119130 result
120131}
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