@@ -28,71 +28,150 @@ impl Display for Condition {
28
28
}
29
29
}
30
30
31
- struct ConditionRecord {
31
+ // State when searching for arrangements
32
+ struct State {
32
33
conditions : Vec < Condition > ,
33
- damaged_spring_groups : Vec < usize > ,
34
+ last_damaged_count : usize ,
35
+ last_damaged_spring_groups_index : usize ,
36
+ invalid : bool ,
37
+ needs_constraint_check : bool ,
34
38
}
35
39
36
- impl ConditionRecord {
37
- #[ cfg( test) ]
38
- pub fn new ( conditions : & [ Condition ] , damaged_spring_groups : & [ usize ] ) -> ConditionRecord {
39
- ConditionRecord {
40
- conditions : conditions. to_vec ( ) ,
41
- damaged_spring_groups : damaged_spring_groups. to_vec ( ) ,
40
+ impl State {
41
+ fn empty ( ) -> State {
42
+ State {
43
+ conditions : vec ! [ ] ,
44
+ last_damaged_count : 0 ,
45
+ last_damaged_spring_groups_index : 0 ,
46
+ invalid : false ,
47
+ needs_constraint_check : false ,
48
+ }
49
+ }
50
+ fn and ( & self , c : Condition ) -> State {
51
+ if self . invalid {
52
+ panic ! ( "cannot 'and' to an invalid state" ) ;
53
+ }
54
+ let mut new_conditions = self . conditions . clone ( ) ;
55
+ new_conditions. push ( c) ;
56
+ State {
57
+ conditions : new_conditions,
58
+ last_damaged_count : self . last_damaged_count ,
59
+ last_damaged_spring_groups_index : self . last_damaged_spring_groups_index ,
60
+ invalid : false ,
61
+ needs_constraint_check : true ,
42
62
}
43
63
}
44
64
65
+
66
+ // Check whether the given conditions violates the constraints given by the `damaged_spring_groups`
67
+ // configuration.
68
+ // If `partial` is true then the check accepts conditions that are incomplete and could be completed
69
+ // to a full valid configuration, if it is false then the conditions must match perfectly.
70
+ fn check_constraints ( & mut self , damaged_spring_groups : & [ usize ] , partial : bool ) -> bool {
71
+ // Invalid states stay invalid, and we can skip checks when nothing has changed
72
+ // since the last check.
73
+ if self . invalid || !self . needs_constraint_check {
74
+ return !self . invalid ;
75
+ }
76
+
77
+ // needs_constraint_check is true on a state that recently got a new condition, so we check
78
+ // whether the last condition is not breaking anything.
79
+ match self . conditions . last ( ) {
80
+ Some ( Condition :: Operational ) => {
81
+ // An operational condition was added, so any incomplete damaged group would now be complete and must match exactly
82
+ // the indicated group.
83
+ if self . last_damaged_count > 0 {
84
+ // A group was open, close it and match it.
85
+ if let Some ( expected_damaged_count) = damaged_spring_groups. get ( self . last_damaged_spring_groups_index ) {
86
+ let result = * expected_damaged_count == self . last_damaged_count ;
87
+ self . invalid = !result;
88
+ } else {
89
+ // State is invalid: keep the constraint check flag on for now, so that the next one
90
+ // will also flag it.
91
+ self . invalid = true ;
92
+ }
93
+ self . last_damaged_count = 0 ;
94
+ self . last_damaged_spring_groups_index += 1 ;
95
+ }
96
+ } ,
97
+ Some ( Condition :: Damaged ) => {
98
+ // A damaged condition was added, so we need to check whether that is still fitting the current index.
99
+ self . last_damaged_count += 1 ;
100
+ if let Some ( expected_damaged_count) = damaged_spring_groups. get ( self . last_damaged_spring_groups_index ) {
101
+ let result = if partial {
102
+ * expected_damaged_count >= self . last_damaged_count
103
+ } else {
104
+ * expected_damaged_count == self . last_damaged_count
105
+ } ;
106
+ self . invalid = !result;
107
+ } else {
108
+ // State is invalid: keep the constraint check flag on for now, so that the next one
109
+ // will also flag it.
110
+ self . invalid = true ;
111
+ }
112
+ } ,
113
+ Some ( condition) => {
114
+ panic ! ( "unexpected condition {}" , condition) ;
115
+ } ,
116
+ None => {
117
+ // Nothing to do really, the thing is empty. But: How did you get here?
118
+ panic ! ( "huh? state is empty" ) ;
119
+ } ,
120
+ }
121
+
122
+ // If we're not doing a partial check, then the group index should point after the end
123
+ // of the groups, i.e. there is no missing group.
124
+ if !self . invalid && !partial {
125
+ self . invalid = self . last_damaged_spring_groups_index >= damaged_spring_groups. len ( ) ;
126
+ }
127
+
128
+ // Mark check as done, and return whether we're now invalid or not.
129
+ self . needs_constraint_check = false ;
130
+ !self . invalid
131
+ }
132
+ }
133
+
134
+ impl Display for State {
135
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
136
+ let cond_str = std:: str:: from_utf8 ( & self . conditions . iter ( ) . map ( |c| * c as u8 ) . collect :: < Vec < u8 > > ( ) ) . unwrap ( ) . to_string ( ) ;
137
+ write ! ( f, "\" {}\" (ldc = {}, i = {}" , cond_str, self . last_damaged_count, self . last_damaged_spring_groups_index)
138
+ }
139
+ }
140
+
141
+ struct ConditionRecord {
142
+ conditions : Vec < Condition > ,
143
+ damaged_spring_groups : Vec < usize > ,
144
+ }
145
+
146
+ impl ConditionRecord {
45
147
pub fn num_arrangements ( & self ) -> usize {
46
148
// Stupid logic: Each '?' can be either '.' or '#'. So, try both, and then proceed
47
149
// and see if _at the end_. Essentially we're building a matching automaton here?????!?!?!!
48
150
// Each state is the conditions we found.
49
- let states: & mut Vec < Vec < Condition > > = & mut vec ! [ ] ;
151
+ let states: & mut Vec < State > = & mut vec ! [ ] ;
50
152
51
153
// Start out with one empty state:
52
- let initial_state = vec ! [ ] ;
53
- states. push ( initial_state) ;
154
+ states. push ( State :: empty ( ) ) ;
54
155
for c in & self . conditions {
55
- // println!("{:?} with states {}", c, states.iter().map(|s| Self::format_state(s)).collect::<Vec<_>>().join(","));
56
-
57
- // Modify the states depending on the condition
58
- // If we see a '.' or '#' then it will be added immediately to the states, which may
59
- // make some switch from "could work" to "invalid".
60
- // If we see a '?' we fork the state, and keep the the valid parts.
61
- // Adding a '.' after a '.' will not actually change anything, so we can skip
62
- // the checks in that case.
63
156
let next_states: Vec < _ > = states. iter ( ) . flat_map ( |s| {
64
- // println!(" ... looking at {}{}", Self::format_state(s), *c as u8 as char);
65
157
match c {
66
158
Condition :: Unknown => {
67
159
let mut tmp = vec ! [ ] ;
68
- let mut s1 = s. clone ( ) ;
69
- s1. push ( Condition :: Damaged ) ;
70
- if !self . violates_constraints ( & s1, true ) {
71
- // println!(" ... {:?} is acceptable", Self::format_state(&s1));
160
+ let mut s1 = s. and ( Condition :: Damaged ) ;
161
+ if s1. check_constraints ( & self . damaged_spring_groups , true ) {
72
162
tmp. push ( s1) ;
73
- } else {
74
- // println!(" ... {:?} violates constraints", Self::format_state(&s1));
75
163
}
76
-
77
- let last = s. last ( ) ;
78
- let mut s2 = s. clone ( ) ;
79
- s2. push ( Condition :: Operational ) ;
80
- if ( last. is_some_and ( |lc| * lc == Condition :: Operational ) && * c == Condition :: Operational ) || !self . violates_constraints ( & s2, true ) {
81
- // println!(" ... {:?} is acceptable", Self::format_state(&s2));
164
+ let mut s2 = s. and ( Condition :: Operational ) ;
165
+ if s2. check_constraints ( & self . damaged_spring_groups , true ) {
82
166
tmp. push ( s2) ;
83
- } else {
84
- // println!(" ... {:?} violates constraints", Self::format_state(&s2));
85
167
}
86
168
tmp
87
169
} ,
88
- _ => {
89
- let last = s. last ( ) ;
90
- let mut s1 = s. clone ( ) ;
91
- s1. push ( * c) ;
92
- if ( last. is_some_and ( |lc| * lc == Condition :: Operational ) && * c == Condition :: Operational ) || !self . violates_constraints ( & s1, true ) {
170
+ c => {
171
+ let mut s1 = s. and ( * c) ;
172
+ if s1. check_constraints ( & self . damaged_spring_groups , true ) {
93
173
vec ! [ s1]
94
174
} else {
95
- // println!(" ... {:?} violates constraints", Self::format_state(&s1));
96
175
vec ! [ ]
97
176
}
98
177
}
@@ -106,65 +185,13 @@ impl ConditionRecord {
106
185
* states = next_states;
107
186
}
108
187
109
- states. iter ( ) . filter ( |s| {
110
- let ok = !self . violates_constraints ( s, false ) ;
111
- // if ok {
112
- // println!("{}", Self::format_state(s));
113
- // }
114
- ok
115
- } ) . count ( )
116
- }
117
-
118
- // Check whether the given conditions violates the constraints given by the `damaged_spring_groups`
119
- // configuration.
120
- // If `partial` is true then the check accepts conditions that are incomplete and could be completed
121
- // to a full valid configuration, if it is false then the conditions must match perfectly.
122
- fn violates_constraints ( & self , conditions : & [ Condition ] , partial : bool ) -> bool {
123
- let mut next_spring_group = self . damaged_spring_groups . iter ( ) ;
124
- let mut damaged_count = 0 ;
125
- for c in conditions {
126
- if * c == Condition :: Operational {
127
- if damaged_count > 0 {
128
- match next_spring_group. next ( ) {
129
- Some ( expected_damaged_count) => {
130
- if * expected_damaged_count != damaged_count {
131
- return true ;
132
- }
133
- } ,
134
- None => {
135
- return true ;
136
- } ,
137
- }
138
- damaged_count = 0 ;
139
- }
140
- } else if * c == Condition :: Damaged {
141
- damaged_count += 1 ;
142
- } else {
143
- panic ! ( "unexpected condition {:?}" , c)
144
- }
145
- }
146
-
147
- // We're only checking whether the given conditions could still pass, so at this point
148
- // we only want "close enough": If we collected some damaged springs, then the next value
149
- // in the iteration must not be smaller than that.
150
- // For a "full" check we would also verify that there is nothing left over at the end, and
151
- // any collected damamged springs would have to match exactly.
152
- match next_spring_group. next ( ) {
153
- Some ( expected_damaged_count) => {
154
- if partial {
155
- damaged_count > * expected_damaged_count
156
- } else {
157
- damaged_count != * expected_damaged_count || next_spring_group. next ( ) . is_some ( )
158
- }
159
- }
160
- None => {
161
- damaged_count > 0
188
+ let mut result = 0 ;
189
+ for s in states. iter_mut ( ) {
190
+ if s. check_constraints ( & self . damaged_spring_groups , false ) {
191
+ result += 1 ;
162
192
}
163
193
}
164
- }
165
-
166
- fn format_state ( state : & [ Condition ] ) -> String {
167
- std:: str:: from_utf8 ( & state. iter ( ) . map ( |c| * c as u8 ) . collect :: < Vec < u8 > > ( ) ) . unwrap ( ) . to_string ( )
194
+ result
168
195
}
169
196
170
197
fn parse_state ( s : & str ) -> Vec < Condition > {
@@ -253,26 +280,34 @@ mod tests {
253
280
}
254
281
}
255
282
283
+ fn build_state ( s : & str , damaged_spring_groups : & [ usize ] ) -> Result < State , & ' static str > {
284
+ let mut result = State :: empty ( ) ;
285
+ for c in s. chars ( ) {
286
+ let cond = Condition :: from ( c) ;
287
+ result = result. and ( cond) ;
288
+ if !result. check_constraints ( damaged_spring_groups, true ) {
289
+ return Err ( "cannot build state" ) ;
290
+ }
291
+ }
292
+ Ok ( result)
293
+ }
294
+
256
295
#[ test]
257
- fn condition_record_violates_constraints_partial ( ) {
258
- assert ! ( !ConditionRecord :: new( & [ ] , & [ ] ) . violates_constraints( & [ ] , true ) , "empty allows empty" ) ;
259
- assert ! ( !ConditionRecord :: new( & [ ] , & [ 1 ] ) . violates_constraints( & [ ] , true ) , "non-empty allows empty" ) ;
260
- assert ! ( !ConditionRecord :: new( & [ ] , & [ 1 ] ) . violates_constraints( & [ Condition :: Damaged ] , true ) , "must match single" ) ;
261
- assert ! ( !ConditionRecord :: new( & [ ] , & [ 2 , 1 ] ) . violates_constraints( & [ Condition :: Damaged , Condition :: Damaged , Condition :: Operational , Condition :: Damaged ] , true ) , "must match multiple with operational in the middle" ) ;
262
- assert ! ( !ConditionRecord :: new( & [ ] , & [ 2 , 2 ] ) . violates_constraints( & [ Condition :: Damaged , Condition :: Damaged , Condition :: Operational , Condition :: Damaged ] , true ) , "last can be incomplete" ) ;
296
+ fn state_check_constraints_partial ( ) {
297
+ assert ! ( State :: empty( ) . check_constraints( & [ ] , true ) , "empty allows empty" ) ;
298
+ let dsg1 = & [ 1 ] ;
299
+ assert ! ( State :: empty( ) . check_constraints( dsg1, true ) , "non-empty allows empty" ) ;
300
+ assert ! ( build_state( "#" , dsg1) . unwrap( ) . check_constraints( dsg1, true ) , "must match single" ) ;
301
+ let dsg21 = & [ 2 , 1 ] ;
302
+ assert ! ( build_state( "##.#" , dsg21) . unwrap( ) . check_constraints( dsg21, true ) , "must match multiple with operational in the middle" ) ;
303
+ let dsg22 = & [ 2 , 2 ] ;
304
+ assert ! ( build_state( "##.#" , dsg22) . unwrap( ) . check_constraints( dsg22, true ) , "last can be incomplete" ) ;
263
305
}
264
306
265
307
#[ test]
266
- fn condition_record_violates_constraints_partial_small ( ) {
267
- assert ! ( ConditionRecord :: new( & [ ] , & [ 3 , 2 , 1 ] ) . violates_constraints( & [
268
- Condition :: Operational ,
269
- Condition :: Damaged ,
270
- Condition :: Damaged ,
271
- Condition :: Damaged ,
272
- Condition :: Operational ,
273
- Condition :: Damaged ,
274
- Condition :: Operational ,
275
- ] , false ) ) ;
308
+ fn state_check_constraints_small ( ) {
309
+ let dsg321 = & [ 3 , 2 , 1 ] ;
310
+ assert ! ( build_state( ".###.#." , dsg321) . is_err( ) ) ;
276
311
}
277
312
278
313
#[ test]
0 commit comments