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

Day 10: Pipe Maze

day-10.livemd

Day 10: Pipe Maze

Part one

sample_input_1 =
  """
  -L|F7
  7S-7|
  L|7||
  -L-J|
  L|-JF
  """

sample_input_2 =
  """
  7-F7-
  .FJ|7
  SJLL7
  |F--J
  LJ.LJ
  """
defmodule PipeMaze do
  def number_of_steps_to_farthest_point(input) do
    map = parse_map(input)

    [_, start_location] = initial_path = get_initial_path(map)

    initial_path
    |> find_loop_path(start_location, map)
    |> length()
    |> div(2)
  end

  defp get_initial_path(map) do
    start_location = get_start_location(map)

    [connected_location_1, _connected_location_2] =
      start_location
      |> get_neighbours(map)
      |> Enum.filter(&(start_location in get_reachable_neighbours(map, &1)))

    [connected_location_1, start_location]
  end

  @neighbour_offsets [{-1, 0}, {0, 1}, {1, 0}, {0, -1}]

  defp get_neighbours(location, map) do
    @neighbour_offsets
    |> Enum.map(&apply_offset(location, &1))
    |> Enum.filter(&is_within_bounds?(&1, map))
  end

  defp is_within_bounds?({row, col}, map),
    do: row >= 0 and row <= length(map) and col >= 0 and col <= length(hd(map))

  defp find_loop_path(acc_path, start_location, map) do
    [hd | rest] = new_path = advance_path(map, acc_path)

    case hd do
      ^start_location -> rest
      _ -> find_loop_path(new_path, start_location, map)
    end
  end

  @direction_offsets %{
    "J" => [{0, -1}, {-1, 0}],
    "7" => [{0, -1}, {1, 0}],
    "L" => [{-1, 0}, {0, 1}],
    "F" => [{1, 0}, {0, 1}],
    "-" => [{0, -1}, {0, 1}],
    "|" => [{-1, 0}, {1, 0}],
    "." => []
  }

  defp advance_path(map, [last, previous | _] = path) do
    next = get_reachable_neighbours(map, last) |> Enum.find(&amp;(&amp;1 != previous))
    [next | path]
  end

  defp get_reachable_neighbours(map, location) do
    symbol = get_symbol_at_location(map, location)

    @direction_offsets[symbol]
    |> Enum.map(&amp;apply_offset(location, &amp;1))
    |> Enum.filter(&amp;is_within_bounds?(&amp;1, map))
  end

  defp apply_offset({row, col}, {row_offset, col_offset}),
    do: {row + row_offset, col + col_offset}

  defp get_start_location(map) do
    start_row = Enum.find_index(map, &amp;("S" in &amp;1))
    start_col = Enum.find_index(Enum.at(map, start_row), &amp;(&amp;1 == "S"))
    {start_row, start_col}
  end

  defp parse_map(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&amp;String.split(&amp;1, "", trim: true))
  end

  defp get_symbol_at_location(map, {row, col}), do: map |> Enum.at(row) |> Enum.at(col)
end
PipeMaze.number_of_steps_to_farthest_point(sample_input_1)
# expected 4
PipeMaze.number_of_steps_to_farthest_point(sample_input_2)
# expected 8
Path.join(__DIR__, "day-10.input")
|> File.read!()
|> PipeMaze.number_of_steps_to_farthest_point()

# 7173

Part two