Day 6: Guard Gallivant
Mix.install([:kino])
Section
input = Kino.Input.textarea("input")
input = Kino.Input.read(input)
Part 1
defmodule GuardGallivantPart1 do
defp parse_input(input) do
String.split(input, "\n") |> Enum.map(&String.split(&1, "", trim: true))
end
defp get_pos(map, x, y) do
Enum.at(map, y) |> Enum.at(x)
end
defp get_start_point(map) do
cols = length(hd(map)) - 1
rows = length(map) - 1
{_start_x, _start_y} =
for x <- 0..cols, y <- 0..rows, get_pos(map, x, y) == "^", reduce: {0, 0} do
_acc ->
{x, y}
end
end
defp out_of_bound?(map, x, y) do
x < 0 || y < 0 || y > length(map) - 1 || x > length(hd(map)) - 1
end
defp next_point(x, y, {dir_x, dir_y}), do: {x + 1 * dir_x, y + 1 * dir_y}
# up
defp next_direction({0, -1}), do: {1, 0}
# right
defp next_direction({1, 0}), do: {0, 1}
# down
defp next_direction({0, 1}), do: {-1, 0}
# left
defp next_direction({-1, 0}), do: {0, -1}
defp run_guard(map, {x, y} = _position, {_dir_x, _dir_y} = dir, visited_points = %MapSet{}) do
visited_points = MapSet.put(visited_points, {x, y})
with {next_x, next_y} <- next_point(x, y, dir),
false <- out_of_bound?(map, next_x, next_y),
point <- get_pos(map, next_x, next_y) do
case point do
"#" ->
run_guard(
map,
next_point(x, y, next_direction(dir)),
next_direction(dir),
visited_points
)
s when s in [".", "^", "O"] ->
run_guard(map, {next_x, next_y}, dir, visited_points)
end
else
_ -> visited_points
end
end
def solve(input) do
map = parse_input(input)
map
|> run_guard(get_start_point(map), {0, -1}, MapSet.new())
|> MapSet.size()
end
end
GuardGallivantPart1.solve(input)
Part 2
defmodule GuardGallivantPart2 do
defp parse_input(input) do
String.split(input, "\n") |> Enum.map(&String.split(&1, "", trim: true))
end
defp get_pos(map, {x, y}) do
get_pos(map, x, y)
end
defp get_pos(map, x, y) do
Enum.at(map, y) |> Enum.at(x)
end
defp get_start_point(map) do
cols = length(hd(map)) - 1
rows = length(map) - 1
{_start_x, _start_y} =
for x <- 0..cols, y <- 0..rows, get_pos(map, x, y) == "^", reduce: {0, 0} do
_acc ->
{x, y}
end
end
defp out_of_bound?(map, {x, y}) do
out_of_bound?(map, x, y)
end
defp out_of_bound?(map, x, y) do
x < 0 || y < 0 || y > length(map) - 1 || x > length(hd(map)) - 1
end
defp next_point(x, y, {dir_x, dir_y}), do: {x + 1 * dir_x, y + 1 * dir_y}
# up
defp next_direction({0, -1}), do: {1, 0}
# right
defp next_direction({1, 0}), do: {0, 1}
# down
defp next_direction({0, 1}), do: {-1, 0}
# left
defp next_direction({-1, 0}), do: {0, -1}
defp is_loop?(visited_points, position, dir) do
MapSet.member?(visited_points, {position, dir})
end
defp make_next_turn(map, {x, y} = cur_pos, {_dx, _dy} = cur_dir) do
next_dir = next_direction(cur_dir)
next_pos = next_point(x, y, next_dir)
cond do
!out_of_bound?(map, next_pos) && get_pos(map, next_pos) == "#" ->
{cur_pos, next_dir}
out_of_bound?(map, next_pos) ->
{cur_pos, next_dir}
true ->
{next_pos, next_dir}
end
end
defp run_guard(map, {x, y} = _position, {_dir_x, _dir_y} = dir, visited_points = %MapSet{}) do
with {next_x, next_y} <- next_point(x, y, dir),
:ok <- if(is_loop?(visited_points, {x, y}, dir), do: :is_loop, else: :ok),
:ok <- if(out_of_bound?(map, next_x, next_y), do: :out_of_bound, else: :ok),
point <- get_pos(map, next_x, next_y) do
case point do
"#" ->
{next_pos, next_dir} = make_next_turn(map, {x, y}, dir)
run_guard(
map,
next_pos,
next_dir,
MapSet.put(visited_points, {{x, y}, dir})
)
s when s in [".", "^"] ->
run_guard(map, {next_x, next_y}, dir, MapSet.put(visited_points, {{x, y}, dir}))
end
else
:is_loop ->
{:loop, visited_points}
:out_of_bound ->
{:ok, visited_points}
end
end
def solve(input) do
map = parse_input(input)
start = get_start_point(map)
points =
for x <- 0..(length(hd(map)) - 1),
y <- 0..(length(map) - 1),
get_pos(map, x, y) == ".",
do: {x, y}
Task.async_stream(
points,
fn {x, y} ->
Enum.at(map, y)
|> List.replace_at(x, "#")
|> then(&List.replace_at(map, y, &1))
|> run_guard(start, {0, -1}, MapSet.new())
end,
ordered: false,
timeout: 5000
)
|> Enum.map(&elem(&1, 1))
|> Enum.reject(&(elem(&1, 0) == :ok))
|> Enum.count()
end
end
GuardGallivantPart2.solve(input)