1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
require 'delegate'
State = Struct.new(:floors, :elevator) do
def self.from_s(input)
elevator = nil
floors = input.lines.reverse.map.with_index {|line, index|
_, e, *items = line.split(/\s+/)
elevator = index if e == ?E
Floor.new(items.reject {|item| item == ?. })
}
new(floors, elevator)
end
def ==(state)
eql?(state)
end
def eql?(state)
elevator == state.elevator && floors.zip(state.floors).all? {|a,b| a.eql?(b) }
end
def candidates
[-1, 1].flat_map {|delta|
next_floor = elevator + delta
next [] unless (0...floors.size).cover?(next_floor)
elevator_items = [1, 2].flat_map {|n| current_floor.to_a.combination(n).to_a }
elevator_items.map {|items| move(items, elevator, next_floor) }
}
end
def irradiated?
floors.any?(&:irradiated?)
end
def to_s
floors.map.with_index {|floor, index|
"#{elevator == index ??E:?.} #{floor.inspect}"
}.reverse.join("\n")
end
private
def current_floor
floors[elevator]
end
def move(items, from, to)
from_floor = Floor.new(floors[from] - items)
to_floor = Floor.new(floors[to] + items)
floors = self.floors.clone
floors[from] = from_floor
floors[to] = to_floor
self.class.new(floors, to)
end
end
class Floor < SimpleDelegator
attr_reader :source
def initialize(items)
@source = super(Set.new(items))
end
def microchips
source.select {|item| item.end_with?(?M) }.map {|item| item.chomp(?M) }
end
def generators
source.select {|item| item.end_with?(?G) }.map {|item| item.chomp(?G) }
end
def shielded
microchips & generators
end
def irradiated?
!(generators.empty? || (microchips - generators).empty?)
end
def hash
id.hash
end
def eql?(floor)
id == floor.id
end
def id
[shielded.size, (microchips - shielded).size, (generators - shielded).size]
end
end
if __FILE__ == $0
INPUT = <<-INPUT
F4 .
F3 . CoM CuM RM PlM
F2 . CoG CuG RG PlG
F1 E PrG PrM EG EM DG DM
INPUT
Step = Struct.new(:state, :count)
seen = Set.new
steps = [Step.new(State.from_s(INPUT), 0)]
until steps.empty? do
step = steps.shift
# puts
# puts "Steps: #{step.count}"
# puts step.state
if step.state.floors[0..-2].all?(&:empty?)
puts
puts "Steps: #{step.count}"
puts step.state
exit
end
step.state.candidates.each do |candidate|
next if seen.include?(candidate)
seen << candidate
next if candidate.irradiated?
steps << Step.new(candidate, step.count + 1)
end
end
end
require 'minitest/autorun'
class TestDay11 < Minitest::Test
INPUT = <<-INPUT
F4 . . . . .
F3 . . . LG .
F2 . HG . . .
F1 E . HM . LM
INPUT
def setup
@state = State.from_s(INPUT)
end
def test_initialize
assert_equal 4, @state.floors.size
assert_equal 0, @state.elevator
floor = @state.floors[0]
assert_equal 2, floor.size
assert_equal %w[ H L ], floor.microchips
assert_empty floor.generators
end
def test_equality
state = State.from_s(<<-INPUT)
F4 . . . . .
F3 . . . HG .
F2 . LG . . .
F1 E . HM . LM
INPUT
assert_equal @state, state
assert_equal @state.hash, state.hash
assert @state.eql?(state)
set = Set.new
set << @state
assert_includes set, @state
assert_includes set, state
end
def test_candidates
candidates = @state.candidates
assert_equal 3, candidates.size
candidate = candidates[0]
assert_equal 1, candidate.elevator
assert_equal %W[ LM ], candidate.floors[0].to_a
assert_equal %W[ HG HM ], candidate.floors[1].to_a
candidate = candidates[1]
assert_equal 1, candidate.elevator
assert_equal %W[ HM ], candidate.floors[0].to_a
assert_equal %W[ HG LM ], candidate.floors[1].to_a
candidate = candidates[2]
assert_equal 1, candidate.elevator
assert_empty candidate.floors[0]
assert_equal %W[ HG HM LM ], candidate.floors[1].to_a
end
def test_irradiated
refute Floor.new(%w[]).irradiated?
refute Floor.new(%w[ BM ]).irradiated?
refute Floor.new(%w[ BG ]).irradiated?
refute Floor.new(%w[ BM BG ]).irradiated?
refute Floor.new(%w[ BM BG LG ]).irradiated?
assert Floor.new(%w[ BM LG ]).irradiated?
end
end