1
0
Fork 0
advent-of-code/2021/lib/2021/15.ex
Sloane Perrault 9b8558c65f 2021, 15 - keep a map set with p queue
actually slower but easier to read
2022-09-21 09:19:53 -04:00

166 lines
3.9 KiB
Elixir

import AOC
defmodule PriorityQueue2 do
defstruct [:p_queue, :map_set]
def new() do
%__MODULE__{
p_queue: PriorityQueue.new(),
map_set: MapSet.new()
}
end
def push(%__MODULE__{p_queue: p_queue, map_set: map_set} = p_queue_2, key, weight) do
if MapSet.member?(map_set, key) do
p_queue_2
else
%__MODULE__{
p_queue: PriorityQueue.push(p_queue, key, weight),
map_set: MapSet.put(map_set, key)
}
end
end
def pop(%__MODULE__{p_queue: p_queue, map_set: map_set} = p_queue_2) do
case PriorityQueue.pop(p_queue) do
{{:value, key} = result, p_queue} ->
{result,
%__MODULE__{
p_queue: p_queue,
map_set: MapSet.delete(map_set, key)
}}
{:empty = result, _} ->
{result, p_queue_2}
end
end
end
aoc 2021, 15 do
use Timex
def input(input_string \\ input_string()) do
lists =
input_string
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
line
|> String.graphemes()
|> Enum.map(&String.to_integer/1)
end)
for {row, i} <- Enum.with_index(lists, 1), {cell, j} <- Enum.with_index(row, 1), into: %{} do
{{i, j}, cell}
end
end
def move({i, j}, {g, h}), do: {g.(i), h.(j)}
def neighbors(map, pos) do
[
{&(&1 + 1), & &1},
{& &1, &(&1 + 1)},
{&(&1 - 1), & &1},
{& &1, &(&1 - 1)}
]
|> Enum.map(&move(pos, &1))
|> Enum.filter(&Map.has_key?(map, &1))
end
def manhattan_dist({x1, y1}, {x2, y2}), do: abs(x1 - x2) + abs(y1 - y2)
def a_star(map, start, goal) do
open =
PriorityQueue2.new()
|> PriorityQueue2.push(start, 0)
g_scores = %{start => 0}
f_scores = %{start => manhattan_dist(start, goal)}
a_star(map, goal, open, g_scores, f_scores)
end
def a_star(map, goal, open, g_scores, f_scores) do
case PriorityQueue2.pop(open) do
{{:value, curr}, open} ->
if curr == goal do
f_scores[curr]
else
{open, g_scores, f_scores} =
for neighbor <- neighbors(map, curr), reduce: {open, g_scores, f_scores} do
{open, g_scores, f_scores} ->
tentative_g_score = g_scores[curr] + Map.get(map, neighbor)
if tentative_g_score < g_scores[neighbor] do
g_scores = Map.put(g_scores, neighbor, tentative_g_score)
f_scores =
Map.put(
f_scores,
neighbor,
tentative_g_score + manhattan_dist(neighbor, goal)
)
open = PriorityQueue2.push(open, neighbor, f_scores[neighbor])
{open, g_scores, f_scores}
else
{open, g_scores, f_scores}
end
end
a_star(map, goal, open, g_scores, f_scores)
end
{:empty, _} ->
:error
end
end
# This can totally be done with math oh well
def clamp_weight(n) do
Stream.cycle(1..9)
|> Stream.drop(n - 1)
|> Enum.take(1)
|> hd()
end
def expand_map(tile, n) do
{tile_x_size, tile_y_size} = tile |> Map.keys() |> Enum.max()
for i <- 0..(n - 1), j <- 0..(n - 1), reduce: %{} do
map ->
Map.merge(
map,
for {{x, y}, w} <- tile, into: %{} do
{{tile_x_size * i + x, tile_y_size * j + y}, clamp_weight(w + i + j)}
end
)
end
end
def lowest_total_risk_to_end(map) do
start = {1, 1}
goal = map |> Map.keys() |> Enum.max()
{duration, result} =
Duration.measure(fn ->
a_star(map, start, goal)
end)
duration = Timex.format_duration(duration, :humanized)
"Result #{result} found in #{duration}"
end
def p1 do
input()
|> lowest_total_risk_to_end()
end
def p2 do
input()
|> expand_map(5)
|> lowest_total_risk_to_end()
end
end