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/2018/ruby/day_17.rb

262 lines
5.7 KiB

require "set"
require "minitest"
require "minitest/pride"
class Slice
def self.parse(s)
squares = {}
s.scan(/^(x|y)=(\d+), (?:x|y)=(\d+)..(\d+)$/m).each do |xy, i, min, max|
i, min, max = [i, min, max].map(&:to_i)
(min..max).map {|j| xy == ?x ? [i, j] : [j, i] }.each do |pos|
squares[pos] = ?#
end
end
new(squares)
end
attr_reader :squares
def initialize(squares)
@squares = squares
@squares[[500, 0]] = ?+
@active = Set[[500, 0]]
end
def simulate
return enum_for(__method__) unless block_given?
max_y = @squares.keys.map(&:last).max
loop do
@active.to_a.each do |pos|
@active.delete(pos)
x, y = pos
case @squares.fetch(pos)
when ?+
@squares[[x, y+1]] = ?|
@active << [x, y+1]
when ?|
next if y+1 > max_y
if @squares[[x, y+1]].nil?
@squares[[x, y+1]] = ?|
@active << [x, y+1]
else
if @squares[[x-1, y]].nil?
@squares[[x-1, y]] = ?|
@active << [x-1, y]
end
if @squares[[x+1, y]].nil?
@squares[[x+1, y]] = ?|
@active << [x+1, y]
end
left_x = x-1
left_x -= 1 until @squares[[left_x, y]] != ?|
right_x = x+1
right_x += 1 until @squares[[right_x, y]] != ?|
if @squares[[left_x, y]] == ?# && @squares[[right_x, y]] == ?#
(left_x+1..right_x-1).each do |x|
@squares[[x, y]] = ?~
@active << [x, y-1] if @squares[[x, y-1]] == ?|
end
end
end
end
end
return if @active.empty?
yield self
end
end
def water
@squares.select {|_, s| %w[ | ~ ].include?(s) }
end
def to_s
min_x, max_x = @squares.keys.map(&:first).minmax
min_y, max_y = @squares.keys.map(&:last).minmax
min_y = [0, min_y].min
(min_y..max_y).map {|y| (min_x-1..max_x+1).map {|x| @squares.fetch([x, y]) { ?. } }.join }.join(?\n)
end
end
class TestSlice < Minitest::Test
def setup
@slice = Slice.parse(<<~SLICE)
x=495, y=2..7
y=7, x=495..501
x=501, y=3..7
x=498, y=2..4
x=506, y=1..2
x=498, y=10..13
x=504, y=10..13
y=13, x=498..504
SLICE
end
def test_parse_to_s
assert_equal <<~SLICE.chomp, @slice.to_s
......+.......
............#.
.#..#.......#.
.#..#..#......
.#..#..#......
.#.....#......
.#.....#......
.#######......
..............
..............
....#.....#...
....#.....#...
....#.....#...
....#######...
SLICE
end
def test_simulate
simulation = @slice.simulate
5.times { simulation.next }
assert_equal <<~SLICE.chomp, @slice.to_s
......+.......
......|.....#.
.#..#.|.....#.
.#..#.|#......
.#..#.|#......
.#....|#......
.#.....#......
.#######......
..............
..............
....#.....#...
....#.....#...
....#.....#...
....#######...
SLICE
5.times { simulation.next }
assert_equal <<~SLICE.chomp, @slice.to_s
......+.......
......|.....#.
.#..#.|.....#.
.#..#.|#......
.#..#.|#......
.#....|#......
.#~~~~~#......
.#######......
..............
..............
....#.....#...
....#.....#...
....#.....#...
....#######...
SLICE
4.times { simulation.next }
assert_equal <<~SLICE.chomp, @slice.to_s
......+.......
......|.....#.
.#..#.|.....#.
.#..#.|#......
.#..#.|#......
.#~~~~~#......
.#~~~~~#......
.#######......
..............
..............
....#.....#...
....#.....#...
....#.....#...
....#######...
SLICE
2.times { simulation.next }
assert_equal <<~SLICE.chomp, @slice.to_s
......+.......
......|.....#.
.#..#.|.....#.
.#..#~~#......
.#..#~~#......
.#~~~~~#......
.#~~~~~#......
.#######......
..............
..............
....#.....#...
....#.....#...
....#.....#...
....#######...
SLICE
21.times { simulation.next }
assert_equal <<~SLICE.chomp, @slice.to_s
......+.......
......|.....#.
.#..#||||...#.
.#..#~~#|.....
.#..#~~#|.....
.#~~~~~#|.....
.#~~~~~#|.....
.#######|.....
........|.....
........|.....
....#~~~~~#...
....#~~~~~#...
....#~~~~~#...
....#######...
SLICE
9.times { simulation.next }
assert_equal <<~SLICE.chomp, @slice.to_s
......+.......
......|.....#.
.#..#||||...#.
.#..#~~#|.....
.#..#~~#|.....
.#~~~~~#|.....
.#~~~~~#|.....
.#######|.....
........|.....
...|||||||||..
...|#~~~~~#|..
...|#~~~~~#|..
...|#~~~~~#|..
...|#######|..
SLICE
assert_equal 57, @slice.water.count
end
end
def solve(input)
slice = Slice.parse(input)
min_y = slice.squares.select {|_,s| s == ?# }.map(&:first).map(&:last).min
simulation = slice.simulate
loop do
simulation.next
end
# puts
# min_x, max_x = slice.water.map(&:first).minmax
# min_y, max_y = slice.water.map(&:last).minmax
# min_y = [0, min_y].min
# puts (min_y..max_y).map {|y| (min_x-1..max_x+1).map {|x| slice.squares.fetch([x, y]) { ?. } }.join }.join(?\n)
# Part One
# slice.water.count {|(_,y),_| y >= min_y }
slice.water.count {|_,s| s == ?~ }
end
if __FILE__ == $0
require "minitest/autorun" and exit if ENV["TEST"]
puts solve(ARGF.read)
end