1
+ //! # Aplenty
2
+ //!
3
+ //! Each rule is converted into a half open interval, including the start but excluding the end.
4
+ //! For example:
5
+ //!
6
+ //! * `x > 10` => `10..4001`
7
+ //! * `m < 20` => `1..20`
8
+ //! * `A` => `1..4001`
9
+ //!
10
+ //! For part one if a category is contained in a range, we send the part to the next rule,
11
+ //! stopping when `A` or `R` is reached.
12
+ //!
13
+ //! For part two we perform range splitting similar to [`Day 5`] that converts the category into
14
+ //! 1, 2 or 3 new ranges, then sends those ranges to the respective rule.
15
+ //!
16
+ //! [`Day 5`]: crate::year2023::day05
1
17
use crate :: util:: hash:: * ;
2
18
use crate :: util:: iter:: * ;
3
19
use crate :: util:: parse:: * ;
4
- use Rule :: * ;
5
-
6
- #[ derive( Clone , Copy , Debug ) ]
7
- enum Rule < ' a > {
8
- Send ( & ' a str ) ,
9
- Less ( usize , u32 , & ' a str ) ,
10
- Greater ( usize , u32 , & ' a str ) ,
11
- Equal ( usize , u32 , & ' a str ) ,
20
+
21
+ pub struct Rule < ' a > {
22
+ start : u32 ,
23
+ end : u32 ,
24
+ category : usize ,
25
+ next : & ' a str ,
12
26
}
13
27
14
28
pub struct Input < ' a > {
15
29
workflows : FastMap < & ' a str , Vec < Rule < ' a > > > ,
16
30
parts : & ' a str ,
17
31
}
18
32
33
+ /// Parse each rule from the first half of the input.
34
+ /// Leaves the second half of the input as a `&str` as it's faster to iterate over each chunk of
35
+ /// four numbers than to first collect into a `vec`.
19
36
pub fn parse ( input : & str ) -> Input < ' _ > {
20
37
let ( prefix, suffix) = input. split_once ( "\n \n " ) . unwrap ( ) ;
21
38
let mut workflows = FastMap :: with_capacity ( 1000 ) ;
@@ -27,8 +44,11 @@ pub fn parse(input: &str) -> Input<'_> {
27
44
28
45
for [ first, second] in iter. chunk :: < 2 > ( ) {
29
46
let rule = if second. is_empty ( ) {
30
- Send ( first)
47
+ // The last rule will match everything so pick category 0 arbitrarily.
48
+ Rule { start : 1 , end : 4001 , category : 0 , next : first }
31
49
} else {
50
+ // Map each category to an index for convenience so that we can store a part
51
+ // in a fixed size array.
32
52
let category = match first. as_bytes ( ) [ 0 ] {
33
53
b'x' => 0 ,
34
54
b'm' => 1 ,
@@ -38,11 +58,12 @@ pub fn parse(input: &str) -> Input<'_> {
38
58
} ;
39
59
40
60
let value: u32 = ( & first[ 2 ..] ) . unsigned ( ) ;
61
+ let next = second;
41
62
63
+ // Convert each rule into a half open range.
42
64
match first. as_bytes ( ) [ 1 ] {
43
- b'<' => Less ( category, value, second) ,
44
- b'>' => Greater ( category, value, second) ,
45
- b'=' => Equal ( category, value, second) ,
65
+ b'<' => Rule { start : 1 , end : value, category, next } ,
66
+ b'>' => Rule { start : value + 1 , end : 4001 , category, next } ,
46
67
_ => unreachable ! ( ) ,
47
68
}
48
69
} ;
@@ -60,42 +81,23 @@ pub fn part1(input: &Input<'_>) -> u32 {
60
81
let Input { workflows, parts } = input;
61
82
let mut result = 0 ;
62
83
84
+ // We only care about the numbers and can ignore all delimeters and whitespace.
63
85
for part in parts. iter_unsigned :: < u32 > ( ) . chunk :: < 4 > ( ) {
64
86
let mut key = "in" ;
65
87
66
- loop {
67
- if key == "A" {
68
- result += part. iter ( ) . sum :: < u32 > ( ) ;
69
- break ;
70
- }
71
- if key == "R" {
72
- break ;
73
- }
74
-
75
- for & rule in & workflows[ key] {
76
- match rule {
77
- Send ( next) => key = next,
78
- Less ( category, value, next) => {
79
- if part[ category] < value {
80
- key = next;
81
- break ;
82
- }
83
- }
84
- Greater ( category, value, next) => {
85
- if part[ category] > value {
86
- key = next;
87
- break ;
88
- }
89
- }
90
- Equal ( category, value, next) => {
91
- if part[ category] == value {
92
- key = next;
93
- break ;
94
- }
95
- }
88
+ while key. len ( ) > 1 {
89
+ // Find the first matching rule.
90
+ for & Rule { start, end, category, next } in & workflows[ key] {
91
+ if start <= part[ category] && part[ category] < end {
92
+ key = next;
93
+ break ;
96
94
}
97
95
}
98
96
}
97
+
98
+ if key == "A" {
99
+ result += part. iter ( ) . sum :: < u32 > ( ) ;
100
+ }
99
101
}
100
102
101
103
result
@@ -104,64 +106,41 @@ pub fn part1(input: &Input<'_>) -> u32 {
104
106
pub fn part2 ( input : & Input < ' _ > ) -> u64 {
105
107
let Input { workflows, .. } = input;
106
108
let mut result = 0 ;
107
- let mut todo = vec ! [ ( "in" , 0 , [ ( 1 , 4000 ) ; 4 ] ) ] ;
109
+ let mut todo = vec ! [ ( "in" , 0 , [ ( 1 , 4001 ) ; 4 ] ) ] ;
108
110
109
111
while let Some ( ( key, index, mut part) ) = todo. pop ( ) {
110
- if key == "A" {
111
- result += part. iter ( ) . map ( |( s, e) | ( e - s + 1 ) as u64 ) . product :: < u64 > ( ) ;
112
- continue ;
113
- }
114
- if key == "R" {
112
+ if key. len ( ) == 1 {
113
+ if key == "A" {
114
+ result += part. iter ( ) . map ( |( s, e) | ( e - s) as u64 ) . product :: < u64 > ( ) ;
115
+ }
115
116
continue ;
116
117
}
117
118
118
- match workflows[ key] [ index] {
119
- Send ( next) => todo. push ( ( next, 0 , part) ) ,
120
- Less ( category, value, next) => {
121
- let ( start, end) = part[ category] ;
122
-
123
- if start >= value {
124
- todo. push ( ( key, index + 1 , part) ) ;
125
- } else if end < value {
126
- todo. push ( ( next, 0 , part) ) ;
127
- } else {
128
- part[ category] = ( start, value - 1 ) ;
129
- todo. push ( ( next, 0 , part) ) ;
130
-
131
- part[ category] = ( value, end) ;
132
- todo. push ( ( key, index + 1 , part) ) ;
133
- }
119
+ let Rule { start : s2, end : e2, category, next } = workflows[ key] [ index] ;
120
+ let ( s1, e1) = part[ category] ;
121
+
122
+ // x1 and x2 are the possible overlap.
123
+ let x1 = s1. max ( s2) ;
124
+ let x2 = e1. min ( e2) ;
125
+
126
+ if x1 >= x2 {
127
+ // No overlap. Check the next rating.
128
+ todo. push ( ( key, index + 1 , part) ) ;
129
+ } else {
130
+ // Range that overlaps with the rating.
131
+ part[ category] = ( x1, x2) ;
132
+ todo. push ( ( next, 0 , part) ) ;
133
+
134
+ // Range before rating.
135
+ if s1 < x1 {
136
+ part[ category] = ( s1, x1) ;
137
+ todo. push ( ( key, index + 1 , part) ) ;
134
138
}
135
- Greater ( category, value, next) => {
136
- let ( start, end) = part[ category] ;
137
-
138
- if end <= value {
139
- todo. push ( ( key, index + 1 , part) ) ;
140
- } else if start > value {
141
- todo. push ( ( next, 0 , part) ) ;
142
- } else {
143
- part[ category] = ( start, value) ;
144
- todo. push ( ( key, index + 1 , part) ) ;
145
-
146
- part[ category] = ( value + 1 , end) ;
147
- todo. push ( ( next, 0 , part) ) ;
148
- }
149
- }
150
- Equal ( category, value, next) => {
151
- let ( start, end) = part[ category] ;
152
-
153
- if start > value || end < value {
154
- todo. push ( ( key, index + 1 , part) ) ;
155
- } else {
156
- part[ category] = ( start, value - 1 ) ;
157
- todo. push ( ( key, index + 1 , part) ) ;
158
139
159
- part[ category] = ( value, value) ;
160
- todo. push ( ( next, 0 , part) ) ;
161
-
162
- part[ category] = ( value + 1 , end) ;
163
- todo. push ( ( key, index + 1 , part) ) ;
164
- }
140
+ // Range after rating.
141
+ if x2 < e1 {
142
+ part[ category] = ( x2, e1) ;
143
+ todo. push ( ( key, index + 1 , part) ) ;
165
144
}
166
145
}
167
146
}
0 commit comments