168 lines
4.9 KiB
Elixir
168 lines
4.9 KiB
Elixir
defmodule Aoc2023.Day17 do
|
|
use Aoc2023
|
|
|
|
defmodule PriorityQueue do
|
|
defstruct [:set]
|
|
def new(contents \\ []), do: %__MODULE__{set: :gb_sets.from_list(contents)}
|
|
|
|
def add(%__MODULE__{set: set}, elem, prio) do
|
|
%__MODULE__{set: :gb_sets.add({prio, elem}, set)}
|
|
end
|
|
|
|
def size(%__MODULE__{set: set}) do
|
|
:gb_sets.size(set)
|
|
end
|
|
|
|
def pop_min(%__MODULE__{set: set}) do
|
|
case :gb_sets.size(set) do
|
|
0 ->
|
|
{nil, new()}
|
|
|
|
_ ->
|
|
{res, set} = :gb_sets.take_smallest(set)
|
|
{res, %__MODULE__{set: set}}
|
|
end
|
|
end
|
|
|
|
defimpl Inspect do
|
|
import Inspect.Algebra
|
|
|
|
def inspect(%PriorityQueue{set: set}, opts) do
|
|
concat(["#PriorityQueue.new(", to_doc(:gb_sets.to_list(set), opts), ")"])
|
|
end
|
|
end
|
|
end
|
|
|
|
def parse(input) do
|
|
lines = input |> lines
|
|
|
|
{
|
|
{lines |> length() |> Kernel.-(1), lines |> head |> String.length() |> Kernel.-(1)},
|
|
lines
|
|
|> Enum.with_index()
|
|
|> Enum.flat_map(fn {row, y} ->
|
|
row
|
|
|> String.to_charlist()
|
|
|> Enum.with_index()
|
|
|> Enum.map(fn {cost, x} -> {{y, x}, String.to_integer(<<cost>>)} end)
|
|
end)
|
|
|> Enum.into(%{})
|
|
}
|
|
end
|
|
|
|
defp a_star(starts, neighbors, heuristic, is_goal) do
|
|
loop = fn recur, explored, frontier ->
|
|
{explored, frontier}
|
|
{{_, current}, frontier} = PriorityQueue.pop_min(frontier)
|
|
|
|
if is_goal.(current) do
|
|
{explored, current}
|
|
else
|
|
cost_to_here = Map.get(explored, current) |> elem(1)
|
|
|
|
{explored, frontier} =
|
|
for {neighbor, cost} <- neighbors.(current), reduce: {explored, frontier} do
|
|
{explored, frontier} ->
|
|
cost_ = cost_to_here + cost
|
|
|
|
if Map.get(explored, neighbor, {nil, cost_}) |> elem(1) < cost_ do
|
|
{explored, frontier}
|
|
else
|
|
{
|
|
Map.put(explored, neighbor, {current, cost_}),
|
|
PriorityQueue.add(frontier, neighbor, cost_ + heuristic.(neighbor))
|
|
}
|
|
end
|
|
end
|
|
|
|
recur.(recur, explored, frontier)
|
|
end
|
|
end
|
|
|
|
{explored, goal} =
|
|
loop.(
|
|
loop,
|
|
starts |> Enum.map(&{&1, {nil, 0}}) |> Enum.into(%{}),
|
|
starts |> Enum.map(&{0, &1}) |> PriorityQueue.new()
|
|
)
|
|
|
|
# This doesn't include the starting point
|
|
path_find = fn recur, point, path ->
|
|
case Map.get(explored, point) do
|
|
nil -> path
|
|
{nil, _} -> path
|
|
{prev, _} -> recur.(recur, prev, [point | path])
|
|
end
|
|
end
|
|
|
|
path_find.(path_find, goal, [])
|
|
end
|
|
|
|
def part1({{my, mx}, grid}) do
|
|
# Nodes are y, x, dir, straight length
|
|
a_star(
|
|
[{0, 0, :d, 0}, {0, 0, :r, 0}],
|
|
fn {y, x, dir, len} ->
|
|
case dir do
|
|
:u ->
|
|
[{y, x - 1, :l, 0}, {y, x + 1, :r, 0}] ++
|
|
if len < 2, do: [{y + 1, x, dir, len + 1}], else: []
|
|
|
|
:d ->
|
|
[{y, x - 1, :l, 0}, {y, x + 1, :r, 0}] ++
|
|
if len < 2, do: [{y - 1, x, dir, len + 1}], else: []
|
|
|
|
:l ->
|
|
[{y + 1, x, :u, 0}, {y - 1, x, :d, 0}] ++
|
|
if len < 2, do: [{y, x - 1, dir, len + 1}], else: []
|
|
|
|
:r ->
|
|
[{y + 1, x, :u, 0}, {y - 1, x, :d, 0}] ++
|
|
if len < 2, do: [{y, x + 1, dir, len + 1}], else: []
|
|
end
|
|
|> Enum.filter(fn {y, x, _, _} -> y in 0..my and x in 0..mx end)
|
|
|> Enum.map(fn {y, x, _, _} = pos -> {pos, Map.get(grid, {y, x})} end)
|
|
end,
|
|
fn {y, x, _, _} -> abs(y - my) + abs(x - mx) end,
|
|
fn {y, x, _, _} -> {y, x} == {my, mx} end
|
|
)
|
|
|> Enum.map(fn {y, x, _, _} -> Map.get(grid, {y, x}) end)
|
|
|> Enum.sum()
|
|
end
|
|
|
|
def part2({{my, mx}, grid}) do
|
|
# Nodes are y, x, dir, straight length
|
|
a_star(
|
|
[{0, 0, :r, 0}, {0, 0, :d, 0}, {0, 0, :u, 0}, {0, 0, :l, 0}],
|
|
fn {y, x, dir, len} ->
|
|
(if(len < 9,
|
|
do:
|
|
case dir do
|
|
:u -> [{y + 1, x, :u, len + 1}]
|
|
:d -> [{y - 1, x, :d, len + 1}]
|
|
:l -> [{y, x - 1, :l, len + 1}]
|
|
:r -> [{y, x + 1, :r, len + 1}]
|
|
end,
|
|
else: []
|
|
) ++
|
|
if(len > 2,
|
|
do:
|
|
case dir do
|
|
:u -> [{y, x - 1, :l, 0}, {y, x + 1, :r, 0}]
|
|
:d -> [{y, x - 1, :l, 0}, {y, x + 1, :r, 0}]
|
|
:l -> [{y + 1, x, :u, 0}, {y - 1, x, :d, 0}]
|
|
:r -> [{y + 1, x, :u, 0}, {y - 1, x, :d, 0}]
|
|
end,
|
|
else: []
|
|
))
|
|
|> Enum.filter(fn {y, x, _, _} -> y in 0..my and x in 0..mx end)
|
|
|> Enum.map(fn {y, x, _, _} = pos -> {pos, Map.get(grid, {y, x})} end)
|
|
end,
|
|
fn {y, x, _, _} -> abs(y - my) + abs(x - mx) end,
|
|
fn {y, x, _, len} -> {y, x} == {my, mx} and len > 2 end
|
|
)
|
|
|> Enum.map(fn {y, x, _, _} -> Map.get(grid, {y, x}) end)
|
|
|> Enum.sum()
|
|
end
|
|
end
|