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

Advent of Code 2023 Day 10 Part 1

2023_day10_part1.livemd

Advent of Code 2023 Day 10 Part 1

Mix.install([
  {:kino_aoc, "~> 0.1.5"}
])

Get Inputs

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2023", "10", System.fetch_env!("LB_SESSION"))

My answer

defmodule Resolver do
  @pipe_map %{
    "-" => %{left: true, right: true},
    "|" => %{up: true, down: true},
    "F" => %{down: true, right: true},
    "7" => %{down: true, left: true},
    "L" => %{up: true, right: true},
    "J" => %{up: true, left: true},
    "S" => %{start: true, up: true, down: true, left: true, right: true}
  }

  @next_direction %{
    up: :down,
    down: :up,
    left: :right,
    right: :left
  }

  def parse(input) do
    input
    |> String.split("\n")
    |> Enum.with_index()
    |> Enum.reduce(%{}, fn {line, row_index}, acc ->
      line_maze =
        line
        |> String.codepoints()
        |> Enum.with_index()
        |> Enum.into(%{}, fn {pipe, col_index} ->
          {
            {row_index, col_index},
            Map.get(@pipe_map, pipe, nil)
          }
        end)
        |> Map.filter(fn {_, route} -> !is_nil(route) end)

      Map.merge(acc, line_maze)
    end)
  end

  def resolve(maze) do
    {position, route} =
      maze
      |> Enum.filter(fn {_, route} ->
        Map.has_key?(route, :start)
      end)
      |> hd()

    Stream.iterate(0, &(&1 + 1))
    |> Enum.reduce_while({position, route, :start}, fn index,
                                                       {next_position, next_route, pre_direction} ->
      {next_position, next_route, pre_direction} =
        search_next(next_position, next_route, pre_direction, maze)

      IO.inspect({next_position, next_route, pre_direction})

      if Map.has_key?(next_route, :start) do
        {:halt, div(index + 1, 2)}
      else
        {:cont, {next_position, next_route, pre_direction}}
      end
    end)
  end

  defp search_next(position, route, pre_direction, maze) do
    [:up, :down, :left, :right]
    |> Enum.filter(&(&1 != pre_direction))
    |> Enum.map(fn direction ->
      get_next(position, route, direction, maze)
    end)
    |> Enum.filter(fn {_, next_route, _} ->
      !is_nil(next_route)
    end)
    |> hd()
  end

  defp get_next({row_index, col_index}, route, direction, maze)
       when is_map_key(route, direction) do
    next_position =
      case direction do
        :up -> {row_index - 1, col_index}
        :down -> {row_index + 1, col_index}
        :left -> {row_index, col_index - 1}
        :right -> {row_index, col_index + 1}
      end

    next_direction = Map.get(@next_direction, direction)

    next_route =
      maze
      |> Map.get(next_position)
      |> check_next(next_direction)

    {next_position, next_route, next_direction}
  end

  defp get_next(_, _, _, _), do: {nil, nil, nil}

  defp check_next(nil, _), do: nil

  defp check_next(next_route, next_direction) when is_map_key(next_route, next_direction) do
    next_route
  end

  defp check_next(_, _), do: nil
end
maze =
  """
  .....
  .S-7.
  .|.|.
  .L-J.
  .....
  """
  |> String.slice(0..-2)
  |> Resolver.parse()
Resolver.resolve(maze)
maze =
  """
  ..F7.
  .FJ|.
  SJ.L7
  |F--J
  LJ...
  """
  |> String.slice(0..-2)
  |> Resolver.parse()
Resolver.resolve(maze)
maze = Resolver.parse(puzzle_input)
Resolver.resolve(maze)