Day 6
Mix.install([
{:kino, "~> 0.14.2"}
])
Part 1
matrix = Kino.FS.file_path("day6_input.txt")
|> File.read!()
|> String.trim()
|> String.split("\n")
|> Enum.map(&String.graphemes/1)
matrix_to_map = fn matrix ->
row_count = length(matrix) - 1
col_count = length(Enum.at(matrix, 0)) - 1
Enum.flat_map(0..row_count, fn row ->
Enum.map(0..col_count, fn col ->
{
{row, col},
Enum.at(matrix, row) |> Enum.at(col)
}
end)
end)
|> Enum.into(%{})
end
grid = matrix_to_map.(matrix)
defmodule GuardMover do
def next_position(direction, {r, c}, grid) do
try_position = case direction do
"^" -> {r - 1, c}
"<" -> {r, c - 1}
">" -> {r, c + 1}
"v" -> {r + 1, c}
end
if Map.fetch!(grid, try_position) == "#" do
turn_right(direction) |> next_position({r, c}, grid)
else
{try_position, direction}
end
end
defp turn_right("^"), do: ">"
defp turn_right("<"), do: "^"
defp turn_right(">"), do: "v"
defp turn_right("v"), do: "<"
def walk_grid(grid, position, direction) do
grid = Map.put(grid, position, :x) # Mark as visited
try do
{new_position, new_direction} = next_position(direction, position, grid)
walk_grid(grid, new_position, new_direction)
rescue
KeyError -> grid
end
end
end
start = System.monotonic_time(:microsecond)
{guard_pos, direction} = Map.filter(grid, fn {_, value} -> value in ["^", "<", ">", "v"] end)
|> Map.to_list()
|> Enum.at(0)
part1_end_grid = GuardMover.walk_grid(grid, guard_pos, direction)
result = Map.filter(part1_end_grid, fn {_, value} -> value == :x end)
|> Enum.count()
IO.puts("Result: #{result}")
elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Finished in #{elapsed / 1000}ms"
Part 2
defmodule GuardLooper do
import GuardMover
def guard_loops?(grid, position, direction, seen \\ MapSet.new()) do
if MapSet.member?(seen, {position, direction}) do
true # We've been here before
else
try do
{new_position, new_direction} = next_position(direction, position, grid)
guard_loops?(
grid,
new_position,
new_direction,
MapSet.put(seen, {position, direction})
)
rescue
KeyError -> false # Exited grid, key not found
end
end
end
end
start = System.monotonic_time(:microsecond)
result = part1_end_grid
|> Map.filter(fn {_, v} -> v == :x end) # Visited spaces
|> Map.keys() # Locations of visited spaces
|> List.delete(guard_pos) # Guard position isn't an obstacle candidate
|> Enum.map(fn loc -> Map.replace(grid, loc, "#") end) # Mark obstacles
|> Enum.map(fn grid -> GuardLooper.guard_loops?(grid, guard_pos, direction) end)
|> Enum.filter(& &1) # Find 'true' values
|> Enum.count() # And count them
IO.puts("Result: #{result}")
elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Finished in #{elapsed / 1000}ms"