You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
advent-of-code/2016/ruby/day_11.rb

204 lines
4.2 KiB

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