Day 15: Beacon Exclusion Zone
Somehow, when I use Julia native ranges the runtime explodes. The current implementation still takes about 30 seconds to run. It could be faster to compute the intersection points between the boundaries of all the exclusion zones.
file:src/day15.jl
module Day15
using DataStructures: SortedSet
export range_at_y, Sensor, read_input
const Pt = @NamedTuple {x::Int, y::Int}
dist(a::Pt, b::Pt) = abs(b.x - a.x) + abs(b.y - a.y)
struct Sensor
pos::Pt
beacon::Pt
end
range_at_y(y) = function(s::Sensor)
d = dist(s.pos, s.beacon)
if abs(s.pos.y - y) < d
xrem = d - abs(y - s.pos.y)
MyRange(s.pos.x - xrem, s.pos.x + xrem)
else
nothing
end
end
Base.parse(::Type{Sensor}, s::AbstractString) =
let m = match(r"Sensor at x=(-?\d+), y=(-?\d+): closest beacon is at x=(-?\d+), y=(-?\d+)", s)
isnothing(m) ? nothing : Sensor((x=parse(Int, m[1]), y=parse(Int, m[2])),
(x=parse(Int, m[3]), y=parse(Int, m[4])))
end
filter_things(it) = filter(!isnothing, it)
read_input(io::IO) = (eachline(io) .|> l -> parse(Sensor, l)) |> filter_things
struct MyRange
start::Int
stop::Int
end
overlap(a::MyRange, b::MyRange) =
max(a.start, b.start) <= min(a.stop, b.stop)
Base.isless(a::MyRange, b::MyRange) =
a.start < b.start || (a.start == b.start && a.stop < b.stop)
function Base.intersect(a::MyRange, b::MyRange)
x, y = max(a.start, b.start), min(a.stop, b.stop)
x <= y ? MyRange(x, y) : nothing
end
struct RangeSet
ranges::Vector{MyRange}
RangeSet(x::MyRange) = new([x])
function RangeSet(xs::Vector{MyRange})
isempty(xs) && return new([])
sort!(xs)
result = [xs[1]]
for x in xs[2:end]
if overlap(x, result[end])
result[end] = MyRange(result[end].start, max(result[end].stop, x.stop))
else
push!(result, x)
end
end
new(result)
end
end
Base.length(r::MyRange) = r.stop - r.start + 1
Base.length(s::RangeSet) = sum(length.(s.ranges))
function part2(input)
for y in 0:4000000
for s in input
m = RangeSet([intersect(range_at_y(y)(s),MyRange(0, 4000000))
for s in input if !isnothing(range_at_y(y)(s))])
if length(m.ranges) == 2
return (m.ranges[1].stop + 1, y)
end
end
end
nothing
end
function main(inp::IO, out::IO)
input = read_input(inp)
part1 = RangeSet([(input .|> range_at_y(2000000) |> filter_things)...])
n_beacons = length(unique(filter(p->p.y==2000000, map(s->s.beacon, input))))
n_sensors = length(filter(p->p.y==2000000, map(s->s.pos, input)))
println(out, "Part 1: $(length(part1)-n_beacons-n_sensors)")
(x, y) = part2(input)
println(out, "Part 2: $(x * 4000000 + y)")
end
end # module