Advent 2024 - Day 6
Mix.install([
{:kino, "~> 0.14.2"}
])
Setup
input = Kino.Input.textarea("Please paste your input file")
input = input
|> Kino.Input.read()
|> String.split("\n")
|> Enum.map(&(String.graphemes(&1)))
defmodule PatrolPlanner do
@movement_map %{
{0, -1} => {1, 0},
{1, 0} => {0, 1},
{0, 1} => {-1, 0},
{-1, 0} => {0, -1}
}
def run(input) do
guard_pos = input
|> Enum.with_index()
|> Enum.find_value(fn {row, y_index} ->
row
|> Enum.with_index()
|> Enum.find_value(fn {cell, x_index} ->
if cell == "^" do
{x_index, y_index}
else
nil
end
end)
end)
movement = {0, -1}
visited = MapSet.new([{guard_pos, movement}])
navigate(input, guard_pos, movement, visited)
end
def cell_value(input, {x, y}) do
if x < 0 or y < 0 or y >= length(input) or x >= input |> Enum.at(0) |> length do
nil
else
input |> Enum.at(y) |> Enum.at(x)
end
end
def navigate(input, {p_x, p_y} = position, {m_x, m_y} = movement, visited) do
next_pos = {p_x + m_x, p_y + m_y}
cell = cell_value(input, next_pos)
if cell == "." or cell == "^" do
visited_key = {next_pos, movement}
if MapSet.member?(visited, visited_key) do
:loop_detected
else
navigate(input, next_pos, movement, MapSet.put(visited, visited_key))
end
else
if cell == nil do
visited
else
next_movement = Map.fetch!(@movement_map, movement)
navigate(input, position, next_movement, visited)
end
end
end
end
Part 1
PatrolPlanner.run(input)
|> Enum.map(fn {pos, _mov} -> pos end)
|> MapSet.new()
|> MapSet.size()
Part 2
handle_column = fn input, {col, y} ->
for {val, x} <- col |> Enum.with_index() do
if val == "#" or val == "^" do
nil
else
modified_input = List.replace_at(input, y, List.replace_at(col, x, "#"))
PatrolPlanner.run(modified_input)
end
end
|> Enum.filter(&(&1 == :loop_detected))
end
input
|> Enum.with_index()
|> Task.async_stream(&(handle_column.(input, &1)))
|> Enum.map(fn {:ok, resp} -> resp end)
|> List.flatten()
|> Enum.count()