Skip to content

Commit 37608e6

Browse files
committed
2023-12 Ruby part 2
1 parent 193aec8 commit 37608e6

File tree

1 file changed

+32
-151
lines changed

1 file changed

+32
-151
lines changed

2023/day12/ruby/solution.rb

Lines changed: 32 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -10,184 +10,65 @@
1010
?###???????? 3,2,1
1111
EXAMPLE
1212

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-
4413
example = example.split("\n").map(&:strip)
4514

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-
7815
def parse(input)
7916
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 + ['.']
8119
groups = line.split(" ").last.split(",").map(&:to_i)
8220

8321
{ springs: springs, groups: groups }
8422
end
8523
end
8624

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-
10925
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)
11229
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
12433
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
13137
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)
13442
end
13543

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..])
14246
end
14347

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)
15750
end
15851

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
16655

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
17059

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)
17262
end
173-
end
17463

64+
total
65+
end
17566

17667
def part1(input)
17768
grid = parse(input)
17869

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))
19172
end
19273
end
19374

@@ -205,4 +86,4 @@ def part2(input)
20586
puts "Part 1 (Example): #{part1(example)}"
20687
puts "Part 1: #{part1(input)}"
20788
puts "Part 2 (Example): #{part2(example)}"
208-
# puts "Part 2: #{part2(input)}"
89+
puts "Part 2: #{part2(input)}"

0 commit comments

Comments
 (0)