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 {
1436 let mut todo = VecDeque :: new ( ) ;
1537 let mut seen = grid. same_size_with ( false ) ;
16- let mut added = grid. same_size_with ( false ) ;
1738 let mut result = 0 ;
1839
1940 for y in 0 ..grid. height {
@@ -23,98 +44,104 @@ pub fn part1(grid: &Grid<u8>) -> i32 {
2344 continue ;
2445 }
2546
47+ // Flood fill each region.
2648 let kind = grid[ point] ;
2749 let mut area = 0 ;
28- let mut perm = 0 ;
50+ let mut perimeter = 0 ;
2951
3052 todo. push_back ( point) ;
3153 seen[ point] = true ;
3254
3355 while let Some ( point) = todo. pop_front ( ) {
3456 area += 1 ;
35- perm += 4 ;
36- added[ point] = true ;
3757
3858 for next in ORTHOGONAL . map ( |o| point + o) {
3959 if grid. contains ( next) && grid[ next] == kind {
4060 if !seen[ next] {
4161 seen[ next] = true ;
4262 todo. push_back ( next) ;
4363 }
44- if added[ next] {
45- perm -= 2 ;
46- }
64+ } else {
65+ perimeter += 1 ;
4766 }
4867 }
4968 }
5069
51- result += area * perm ;
70+ result += area * perimeter ;
5271 }
5372 }
5473
5574 result
5675}
5776
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+
6381 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 ( ) ;
6485
6586 for y in 0 ..grid. height {
6687 for x in 0 ..grid. width {
6788 let point = Point :: new ( x, y) ;
68- if seen[ point] {
89+ if seen[ point] != - 1 {
6990 continue ;
7091 }
7192
7293 let kind = grid[ point] ;
73- let mut size = 0 ;
74- let mut sides = 0 ;
94+ let id = y * grid. width + x;
7595
7696 todo. push_back ( point) ;
77- seen[ point] = true ;
97+ seen[ point] = id ;
7898
7999 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) ;
93101
94102 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 ;
97105 todo. push_back ( next) ;
98106 }
99107 }
100108 }
101109
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 ( ) ;
105111
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 ] ;
111117 }
112-
113- corner. clear ( ) ;
114- middle. clear ( ) ;
115- result += size * sides;
116118 }
117119 }
118120
119121 result
120122}
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