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

Advent 2024 - Day 6

day6.livemd

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(&amp;(&amp;1 == :loop_detected))
end

input
|> Enum.with_index()
|> Task.async_stream(&amp;(handle_column.(input, &amp;1)))
|> Enum.map(fn {:ok, resp} -> resp end)
|> List.flatten()
|> Enum.count()