2
2
//!
3
3
//! Solves both parts simultaneously by flood filling each region.
4
4
//!
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 .
7
7
//!
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.
11
11
//!
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:
19
13
//!
20
14
//! ```none
21
- //! .#
22
- //! ## ✓
15
+ //! ... ... .#. > 1
16
+ //! .#. > 2 .#. > 1 .#. > 0
17
+ //! ... .#. > 1 .#. > 1
23
18
//! ```
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.
27
19
use crate :: util:: grid:: * ;
28
20
use crate :: util:: point:: * ;
29
- use std:: array:: from_fn;
30
21
31
22
type Input = ( usize , usize ) ;
32
23
33
24
pub fn parse ( input : & str ) -> Input {
34
25
let grid = Grid :: parse ( input) ;
35
- let lut = sides_lut ( ) ;
36
26
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 ) ;
39
30
40
31
let mut part_one = 0 ;
41
32
let mut part_two = 0 ;
@@ -44,48 +35,54 @@ pub fn parse(input: &str) -> Input {
44
35
for x in 0 ..grid. width {
45
36
// Skip already filled points.
46
37
let point = Point :: new ( x, y) ;
47
- if seen[ point] > 0 {
38
+ if seen[ point] {
48
39
continue ;
49
40
}
50
41
51
- // Assign a unique id to each region based on the first point that we encounter .
42
+ // Flood fill, using area as an index .
52
43
let kind = grid[ point] ;
53
- let id = y * grid. width + x + 1 ;
44
+ let check = |point| grid. contains ( point ) && grid [ point ] == kind ;
54
45
55
- // Flood fill, using area as an index.
56
46
let mut area = 0 ;
57
47
let mut perimeter = 0 ;
58
48
let mut sides = 0 ;
59
49
60
- region . push ( point) ;
61
- seen[ point] = id ;
50
+ todo . push ( point) ;
51
+ seen[ point] = true ;
62
52
63
- while area < region . len ( ) {
64
- let point = region [ area] ;
53
+ while area < todo . len ( ) {
54
+ let point = todo [ area] ;
65
55
area += 1 ;
66
56
67
- for next in ORTHOGONAL . map ( |o| point + o) {
68
- if grid. contains ( next) && grid[ next] == kind {
69
- if seen[ next] == 0 {
70
- region. push ( next) ;
71
- seen[ next] = id;
57
+ for direction in ORTHOGONAL {
58
+ let next = point + direction;
59
+
60
+ if check ( next) {
61
+ if !seen[ next] {
62
+ todo. push ( next) ;
63
+ seen[ next] = true ;
72
64
}
73
65
} else {
66
+ edge. push ( ( point, direction) ) ;
74
67
perimeter += 1 ;
75
68
}
76
69
}
77
70
}
78
71
79
72
// 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] ;
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 ;
85
79
}
86
80
81
+ todo. clear ( ) ;
82
+ edge. clear ( ) ;
83
+
87
84
part_one += area * perimeter;
88
- part_two += area * sides;
85
+ part_two += area * ( sides / 2 ) ;
89
86
}
90
87
}
91
88
@@ -99,19 +96,3 @@ pub fn part1(input: &Input) -> usize {
99
96
pub fn part2 ( input : & Input ) -> usize {
100
97
input. 1
101
98
}
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