10
10
?###???????? 3,2,1
11
11
EXAMPLE
12
12
13
- "" "
14
- ?###????????
15
- .###.##.#...
16
- .###..##.#..
17
- .###...##.#. x
18
- .###....##.# x
19
- .###.##..#..
20
- .###.##...#. x
21
- .###.##....# x
22
- .###..##..#. x
23
- .###..##...# x
24
- .###...##..# x
25
-
26
- .###....##.# x
27
- .###...##..# x
28
- .###...##.#. x
29
- .###..##...# x
30
- .###..##..#. x
31
- .###.##....# x
32
- .###.##...#. x
33
-
34
- ???????
35
- ...##.#
36
- ..##..#
37
- ..##.#.
38
- .##...#
39
- .##..#.
40
- ##....#
41
- ##...#.
42
- " ""
43
-
44
13
example = example . split ( "\n " ) . map ( &:strip )
45
14
46
- class Cell
47
- attr_accessor :status
48
-
49
- STATUS = {
50
- :working => "." ,
51
- :broken => "#" ,
52
- :unknown => "?" ,
53
- }
54
-
55
- STATUS . keys . each do |status |
56
- define_method ( "#{ status } ?" ) do
57
- @status == status
58
- end
59
- self . class . send ( :define_method , status ) do
60
- new ( STATUS [ status ] )
61
- end
62
- end
63
-
64
- def initialize ( char )
65
- @status = STATUS . key ( char )
66
- end
67
-
68
- def to_s
69
- STATUS [ status ]
70
- end
71
-
72
- def inspect
73
- to_s
74
- # "Cell<#{status}>"
75
- end
76
- end
77
-
78
15
def parse ( input )
79
16
input . map do |line |
80
- springs = line . split ( " " ) . first . chars . map ( &Cell . method ( :new ) )
17
+ # Add a working spring at the end of a line to be able to ignore the possiblity of a broken spring at the end
18
+ springs = line . split ( " " ) . first . chars + [ '.' ]
81
19
groups = line . split ( " " ) . last . split ( "," ) . map ( &:to_i )
82
20
83
21
{ springs : springs , groups : groups }
84
22
end
85
23
end
86
24
87
- def assert ( condition , message )
88
- raise StandardError . new ( message ) unless condition
89
- end
90
-
91
- def debug_puts ( *args )
92
- # puts(*args)
93
- end
94
-
95
- def consume_group ( springs , groups )
96
- assert ( groups . size >= 1 , "No group left to consume, but broken springs expected" )
97
- size , *tail_groups = groups
98
-
99
- consumed = springs . take ( size )
100
- peek , *tail_springs = springs . drop ( size )
101
-
102
- valid_consume = consumed . all? { _1 . broken? || _1 . unknown? } && ( peek . nil? || peek . working? || peek . unknown? )
103
-
104
- debug_puts "Consume #{ size } : [#{ consumed . map ( &:to_s ) . join ( '' ) } |#{ peek &.to_s } |#{ tail_springs . map ( &:to_s ) . join ( '' ) } ] -> #{ valid_consume } "
105
-
106
- return valid_consume
107
- end
108
-
109
25
CACHE = { }
110
- def key ( springs , groups )
111
- [ springs . map ( &:to_s ) . join ( '' ) , groups . join ( ',' ) ]
26
+ def cached_count ( s , groups , prev_broken = 0 )
27
+ cache_key = [ s , groups , prev_broken ] . hash
28
+ CACHE [ cache_key ] ||= count ( s , groups , prev_broken )
112
29
end
113
-
114
- def memoized_possible_arrangements ( springs , groups )
115
- CACHE [ key ( springs , groups ) ] ||= possible_arrangements ( springs , groups )
116
- end
117
-
118
- def possible_arrangements ( springs , groups )
119
- debug_puts "Check [#{ springs . map ( &:to_s ) . join ( ) } ] #{ groups } "
120
- # Early stop
121
- if ( groups . sum > springs . size )
122
- debug_puts "Early stop"
123
- return [ false ]
30
+ def count ( s , groups , prev_broken = 0 )
31
+ if groups . empty? && s . any? { _1 == '#' }
32
+ return 0
124
33
end
125
- if groups . empty?
126
- if springs . any? ( &:broken? )
127
- return [ false ] # Illegal solution
128
- else
129
- return [ springs . map { Cell . working } ]
130
- end
34
+
35
+ if s . empty? && groups . empty?
36
+ return 1
131
37
end
132
- if springs . empty? && groups . any?
133
- return [ false ] # Illegal solution
38
+
39
+ total = 0
40
+ if s [ 0 ] == '.' && prev_broken == 0
41
+ total += cached_count ( s [ 1 ..] , groups )
134
42
end
135
43
136
- head , *tail = springs
137
- if head . working?
138
- return possible_arrangements ( tail , groups ) . filter_map do |arrangement |
139
- next if arrangement == false # no solution
140
- [ head , *arrangement ]
141
- end
44
+ if s [ 0 ] == '.' && prev_broken == groups &.first
45
+ total += cached_count ( s [ 1 ..] , groups [ 1 ..] )
142
46
end
143
47
144
- if head . broken?
145
- return [ false ] unless consume_group ( springs , groups ) # Group can not be consumed
146
-
147
- group , *tail_groups = groups
148
- spring_rest = springs . drop ( group + 1 )
149
- prefix = [ Cell . broken ] * group + ( if springs . size > group then [ Cell . working ] else [ ] end )
150
-
151
- debug_puts "Group #{ group } , Prefix #{ prefix . map ( &:to_s ) . join ( '' ) } , Rest: #{ springs . drop ( group + 1 ) . map ( &:to_s ) . join ( '' ) } "
152
-
153
- return possible_arrangements ( springs . drop ( group + 1 ) , tail_groups ) . filter_map do |arrangement |
154
- next if arrangement == false # no solution
155
- [ *prefix , *arrangement ]
156
- end
48
+ if s [ 0 ] == '#' && prev_broken < ( groups . first || 0 )
49
+ total += cached_count ( s [ 1 ..] , groups , prev_broken + 1 )
157
50
end
158
51
159
- if head . unknown?
160
- assume_working = [ Cell . working , *tail ]
161
- assume_broken = [ Cell . broken , *tail ]
162
-
163
- debug_puts "Assume working: #{ assume_working . map ( &:to_s ) . join ( '' ) } "
164
- working = possible_arrangements ( assume_working , groups )
165
- debug_puts "Working: [#{ working . map ( &:to_s ) . join ( '' ) } ]"
52
+ if s [ 0 ] == '?' && prev_broken == 0
53
+ total += cached_count ( s [ 1 ..] , groups )
54
+ end
166
55
167
- debug_puts "Assume broken: #{ assume_broken . map ( & :to_s ) . join ( '' ) } "
168
- broken = possible_arrangements ( assume_broken , groups )
169
- debug_puts "Broken: [ #{ broken . map ( & :to_s ) . join ( '' ) } ]"
56
+ if s [ 0 ] == '?' && prev_broken == groups &. first
57
+ total += cached_count ( s [ 1 .. ] , groups [ 1 .. ] )
58
+ end
170
59
171
- return ( broken + working ) . reject { _1 == false }
60
+ if s [ 0 ] == '?' && prev_broken < ( groups . first || 0 )
61
+ total += cached_count ( s [ 1 ..] , groups , prev_broken + 1 )
172
62
end
173
- end
174
63
64
+ total
65
+ end
175
66
176
67
def part1 ( input )
177
68
grid = parse ( input )
178
69
179
- # puts possible_arrangements("???????".chars.map { Cell.new(_1) }, [2, 1]).map { _1.join('') }.join("\n")
180
-
181
- grid . sum do |row |
182
- row => { springs :, groups : }
183
- arrangements = possible_arrangements ( springs , groups )
184
-
185
- # puts "#{springs.map(&:to_s).join('').rjust(20, ' ')} #{arrangements.size.to_s.rjust(3, ' ')}"
186
- # puts arrangements.map { _1.join('') }.join(', ')
187
- # puts "#{arrangements.map { _1.map(&:to_s).join('') }.join("\n")}"
188
- # puts "\n" * 2
189
-
190
- arrangements . size
70
+ grid . sum do |row , i |
71
+ count ( *row . values_at ( :springs , :groups ) )
191
72
end
192
73
end
193
74
@@ -205,4 +86,4 @@ def part2(input)
205
86
puts "Part 1 (Example): #{ part1 ( example ) } "
206
87
puts "Part 1: #{ part1 ( input ) } "
207
88
puts "Part 2 (Example): #{ part2 ( example ) } "
208
- # puts "Part 2: #{part2(input)}"
89
+ puts "Part 2: #{ part2 ( input ) } "
0 commit comments