advent-of-code/2021/ruby/day_22.rb

110 lines
2.5 KiB

require "set"
steps = ARGF.read.scan(/(on|off) x=(.+),y=(.+),z=(.+)/)
.map {|power, *ranges| [power == "on", *ranges.map { eval(_1) }] }
# .select {|_, *ranges| ranges.all? { _1.begin >= -50 && _1.end <= 50 }}
# reactor = Hash.new(false)
# steps.each do |power,x,y,z|
# x.each do |x|
# y.each do |y|
# z.each do |z|
# reactor[[x,y,z]] = power
# end
# end
# end
# end
# p reactor.count { _2 }
# xs = steps.flat_map { [_2.begin, _2.end] }.minmax
# ys = steps.flat_map { [_3.begin, _3.end] }.minmax
# zs = steps.flat_map { [_4.begin, _4.end] }.minmax
# ons = Range.new(*xs).sum {|x| Range.new(*ys).sum {|y| Range.new(*zs).count {|z|
# step = steps.find { _2.cover?(x) && _3.cover?(y) && _4.cover?(z) }
# step ? step[0] : false
# }}}
# p ons
class Range
def overlap(other)
if cover?(other.begin)
min = other.begin
max = [self.end, other.end].min
(min..max)
elsif cover?(other.end)
min = [self.begin, other.begin].max
max = other.end
(min..max)
elsif other.cover?(self.begin)
min = self.begin
max = [self.end, other.end].min
(min..max)
elsif other.cover?(self.end)
min = [self.begin, other.begin].max
max = self.end
(min..max)
else
nil
end
end
def split(other)
overlap = self.overlap(other)
return [] if overlap.nil?
splits = [ overlap ]
splits << (self.begin..overlap.begin-1) if overlap.begin > self.begin
splits << (overlap.end+1..self.end) if overlap.end < self.end
splits
end
end
Cube = Struct.new(:xs, :ys, :zs) do
def -(other)
return self unless o = overlap(other)
xxs = xs.split(other.xs)
yys = ys.split(other.ys)
zzs = zs.split(other.zs)
cuboids = xxs.flat_map {|x| yys.flat_map {|y| zzs.map {|z|
Cube.new(x, y, z)
}}}
cuboids.delete(o)
cuboids
end
def overlap(other)
overlaps = to_a.zip(other.to_a).map { _1.overlap(_2) }
return nil if overlaps.any?(&:nil?)
Cube.new(*overlaps)
end
def overlap?(other) = !overlap(other).nil?
def volume = (xs.end - xs.begin + 1) * (ys.end - ys.begin + 1) * (zs.end - zs.begin + 1)
def to_s = "[#{[xs, ys, zs].map(&:to_s).join(", ")}]"
alias_method :inspect, :to_s
end
steps = steps.map {|power, *ranges| [power, Cube.new(*ranges)] }
reactor = Set.new
steps.each do |power, cube|
reactor
.select { _1.overlap?(cube) }
.each do |overlap|
reactor.delete(overlap)
reactor.merge(overlap - cube)
end
reactor << cube if power
end
p reactor.sum(&:volume)