diff --git a/2023/ruby/day_12.rb b/2023/ruby/day_12.rb
index 97cfc1d..ddc6494 100644
--- a/2023/ruby/day_12.rb
+++ b/2023/ruby/day_12.rb
@@ -1,14 +1,70 @@
-p ARGF.readlines(chomp: true).map {|row|
+input = ARGF.readlines(chomp: true).map {|row|
springs, groups = row.split(" ")
[springs, groups.scan(/\d+/).map(&:to_i)]
-}.map {|springs, groups|
- unknowns = springs.count(??)
- (0...2**unknowns).map {|arrangement|
- arrangement = arrangement.to_s(2).rjust(unknowns, ?0).chars.map { _1 == ?0 ? ?. : ?# }
- candidate = springs.gsub(??) { arrangement.shift }
- candidate.chars
- .slice_when { _1 != _2 }
- .reject { _1[0] == ?. }
- .map(&:size) == groups
- }.count(&:itself)
-}.sum
+}
+
+p input
+ # .map { [ _1.chars, _2 ] }
+ .map { [Array.new(5, _1).join(??).chars, _2 * 5] }
+ .map {|springs, groups|
+ arrangement_counts = Hash.new {|h,k|
+ count, springs, groups = k
+ first, *rest = springs
+ h[k] = case [count, first, groups]
+ in count, _, []
+ if count.nonzero?
+ 0
+ elsif springs.include?(?#)
+ 0
+ else
+ 1
+ end
+ in count, _, groups if count > groups.fetch(0)
+ 0 # early exit if the damaged group ever gets too large
+ in count, nil, groups # no more springs
+ if groups.size > 1
+ 0
+ elsif count == groups.fetch(0)
+ 1
+ else
+ 0
+ end
+ in count, ?#, groups # continue a damaged group
+ h[[count + 1, rest, groups]]
+ in 0, ?., groups
+ h[[0, rest, groups]]
+ in count, ?., groups # finish a damaged group
+ if count == groups.fetch(0)
+ h[[0, rest, groups[1..]]]
+ else
+ 0
+ end
+ in count, ??, g if count == g.fetch(0) # unknown spring, matched current group
+ h[[0, rest, g[1..]]]
+ in count, ??, g unless count.zero? # unknown spring, ongoing group
+ h[[count + 1, rest, g]]
+ in count, ??, g # unknown spring
+ [
+ h[[count, rest, g]], # undamaged
+ h[[count + 1, rest, g]], # damaged
+ ].sum
+ else
+ fail "#{{first:, springs: springs.join, groups:}}"
+ end
+ }
+ arrangement_counts[[0, springs, groups]]
+ }.sum
+
+# original part one
+# p input.map {|springs, groups|
+# unknowns = springs.count(??)
+# (0...2**unknowns).map {|arrangement|
+# arrangement = arrangement.to_s(2).rjust(unknowns, ?0).chars.map { _1 == ?0 ? ?. : ?# }
+# candidate = springs.gsub(??) { arrangement.shift }
+# candidate.chars
+# .slice_when { _1 != _2 }
+# .reject { _1[0] == ?. }
+# .map(&:size) == groups
+# }.count(&:itself)
+# }.sum
+