22//!
33//! Solves both parts simultaneously by flood filling each region.
44//!
5- //! For part one we increment the perimeter for each neighbouring plot out of bounds
6- //! or a different kind of plant .
5+ //! For part one we increment the perimeter for each neighbouring plot belonging to a different
6+ //! region or out of bounds .
77//!
8- //! For part two we count corners, as the number of corners equals the number of sides.
9- //! We remove a corner when another plot is adjacent either up, down, left or right.
10- //! For example, considering the top left corner of the plot:
8+ //! For part two we count each plot on the edge as either 0, 1 or 2 sides then divide by 2 .
9+ //! An edge plot contributes nothing if it has 2 edge neighbours facing the same way,
10+ //! one if has a single neighbour and two if it has no neighbours.
1111//!
12- //! ```none
13- //! .. .# .. ##
14- //! .# ✓ .# ✗ ## ✗ ## ✗
15- //! ```
16- //!
17- //! However we add back a corner when it's concave, for example where a plot is above, left but
18- //! not above and to the left:
12+ //! For example, considering the right edge:
1913//!
2014//! ```none
21- //! .#
22- //! ## ✓
15+ //! ... ... .#. > 1
16+ //! .#. > 2 .#. > 1 .#. > 0
17+ //! ... .#. > 1 .#. > 1
2318//! ```
24- //!
25- //! There are 8 neighbours to check, giving 2⁸ possibilities. To speed things up these are
26- //! precomputed and cached in a lookup table.
2719use crate :: util:: grid:: * ;
2820use crate :: util:: point:: * ;
29- use std:: array:: from_fn;
3021
3122type Input = ( usize , usize ) ;
3223
3324pub fn parse ( input : & str ) -> Input {
3425 let grid = Grid :: parse ( input) ;
35- let lut = sides_lut ( ) ;
3626
37- let mut region = Vec :: new ( ) ;
38- let mut seen = grid. same_size_with ( 0 ) ;
27+ let mut todo = Vec :: new ( ) ;
28+ let mut edge = Vec :: new ( ) ;
29+ let mut seen = grid. same_size_with ( false ) ;
3930
4031 let mut part_one = 0 ;
4132 let mut part_two = 0 ;
@@ -44,48 +35,54 @@ pub fn parse(input: &str) -> Input {
4435 for x in 0 ..grid. width {
4536 // Skip already filled points.
4637 let point = Point :: new ( x, y) ;
47- if seen[ point] > 0 {
38+ if seen[ point] {
4839 continue ;
4940 }
5041
51- // Assign a unique id to each region based on the first point that we encounter.
52- let kind = grid[ point] ;
53- let id = y * grid. width + x + 1 ;
54-
5542 // Flood fill, using area as an index.
43+ let kind = grid[ point] ;
5644 let mut area = 0 ;
5745 let mut perimeter = 0 ;
58- let mut sides = 0 ;
5946
60- region . push ( point) ;
61- seen[ point] = id ;
47+ todo . push ( point) ;
48+ seen[ point] = true ;
6249
63- while area < region . len ( ) {
64- let point = region [ area] ;
50+ while area < todo . len ( ) {
51+ let point = todo [ area] ;
6552 area += 1 ;
6653
67- for next in ORTHOGONAL . map ( |o| point + o) {
54+ for direction in ORTHOGONAL {
55+ let next = point + direction;
56+
6857 if grid. contains ( next) && grid[ next] == kind {
69- if seen[ next] == 0 {
70- region . push ( next) ;
71- seen[ next] = id ;
58+ if ! seen[ next] {
59+ todo . push ( next) ;
60+ seen[ next] = true ;
7261 }
7362 } else {
63+ edge. push ( ( point, direction) ) ;
7464 perimeter += 1 ;
7565 }
7666 }
7767 }
7868
7969 // Sum sides for all plots in the region.
80- for point in region. drain ( ..) {
81- let index = DIAGONAL . iter ( ) . fold ( 0 , |acc, & d| {
82- ( acc << 1 ) | ( seen. contains ( point + d) && seen[ point + d] == id) as usize
83- } ) ;
84- sides += lut[ index] ;
70+ let check = |point| grid. contains ( point) && grid[ point] == kind;
71+ let mut sides = 0 ;
72+
73+ for & ( p, d) in & edge {
74+ let r = d. clockwise ( ) ;
75+ let l = d. counter_clockwise ( ) ;
76+
77+ sides += ( !check ( p + l) || check ( p + l + d) ) as usize ;
78+ sides += ( !check ( p + r) || check ( p + r + d) ) as usize ;
8579 }
8680
81+ todo. clear ( ) ;
82+ edge. clear ( ) ;
83+
8784 part_one += area * perimeter;
88- part_two += area * sides;
85+ part_two += area * ( sides / 2 ) ;
8986 }
9087 }
9188
@@ -99,19 +96,3 @@ pub fn part1(input: &Input) -> usize {
9996pub fn part2 ( input : & Input ) -> usize {
10097 input. 1
10198}
102-
103- /// There are 8 neighbours to check, giving 2⁸ possibilities.
104- /// Precompute the number of corners once into a lookup table to speed things up.
105- fn sides_lut ( ) -> [ usize ; 256 ] {
106- from_fn ( |neighbours| {
107- let [ up_left, up, up_right, left, right, down_left, down, down_right] =
108- from_fn ( |i| neighbours & ( 1 << i) > 0 ) ;
109-
110- let ul = !( up || left) || ( up && left && !up_left) ;
111- let ur = !( up || right) || ( up && right && !up_right) ;
112- let dl = !( down || left) || ( down && left && !down_left) ;
113- let dr = !( down || right) || ( down && right && !down_right) ;
114-
115- ( ul as usize ) + ( ur as usize ) + ( dl as usize ) + ( dr as usize )
116- } )
117- }
0 commit comments