Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 6

day6.livemd

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(&amp; &amp;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"