Day 12: Hill Climbing Algorithm

file:src/day12.jl
module Day12

using Base: splat
using DataStructures: PriorityQueue, enqueue!, dequeue!
export read_input, part1, trace_back

function read_input(io::IO)
    data = foldl(hcat, Vector{Char}.(readlines(io)))
    target = findfirst(c -> c == 'E', data)
    start = findfirst(c -> c == 'S', data)
    data[start] = 'a'
    data[target] = 'z'
    (height = data .- 'a', start = start, target = target)
end

function grid_dijkstra(
        DistType::Type{T}, size, 
        start::CartesianIndex, istarget::Function,
        neighbours::Function, dist_func::Function) where {T}
    visited = fill(false, size)
    distance = fill(typemax(T), size)
    distance[start] = zero(T)
    queue = PriorityQueue{CartesianIndex,T}()
    prev = Matrix{CartesianIndex}(undef, size)
    enqueue!(queue, start, zero(T))
    current = nothing
    while !isempty(queue)
        current = dequeue!(queue)
        istarget(current) && break
        visited[current] && continue
        for loc in neighbours(current)
            visited[loc] && continue
            d = distance[current] + dist_func(current, loc)
            if d < distance[loc]
                distance[loc] = d
                prev[loc] = current
                enqueue!(queue, loc, d)
            end
        end
        visited[current] = true
    end
    (distance = distance, route = prev, target = current)
end

const grid_neighbours =
    eachrow([0 1; 0 -1; 1 0; -1 0]) .|> splat(CartesianIndex)

function trace_back(prev, start, target)
    route = [target]
    current = target
    while current != start
        current = prev[current]
        pushfirst!(route, current)
    end
    route
end

function main(inp::IO, out::IO)
    input = read_input(inp)
    ok_step(a, b) = input.height[b] - input.height[a] <= 1
    in_bounds(a) = checkbounds(Bool, input.height, a)
    dist_func(a, b) = 1

    function part1()
        neighbours(pt) = (nb for nb in (grid_neighbours .+ (pt,)) if in_bounds(nb) && ok_step(pt, nb))
        grid_dijkstra(Int,
            size(input.height),
            input.start,
            x -> x == input.target,
            neighbours, dist_func)
    end

    function part2()
        neighbours(pt) = (nb for nb in (grid_neighbours .+ (pt,)) if in_bounds(nb) && ok_step(nb, pt))
        grid_dijkstra(Int,
            size(input.height),
            input.target,
            x -> input.height[x] == 0,
            neighbours, dist_func)
    end

    result = part1()
    println(out, "Part 1: $(result.distance[result.target])")
    result = part2()
    println(out, "Part 2: $(result.distance[result.target])")
end

end  # module