Powered by AppSignal & Oban Pro

Advent of code day 16

2023/livebooks/day-16.livemd

Advent of code day 16

Mix.install([
  {:kino, "~> 0.5.0"}
])

Setup input

example = Kino.Input.textarea("Please paste your input example:")
input = Kino.Input.textarea("Please paste your real input:")
parsed = example
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.map(&(String.split(&1, "", trim: true) |> List.to_tuple()))
  |> List.to_tuple()

rows = tuple_size(parsed) - 1
cols = tuple_size(elem(parsed, 0)) - 1

grid =
  for l <- 0..rows, c <- 0..cols, into: %{} do
    {{l, c}, elem(elem(parsed, l), c)}
  end
|> Enum.sort
|> Enum.into(%{})
defmodule Solver2 do
  # map, {r,c, dr, dc}
  def solve(grid, start_point) do
    seen = MapSet.new()
    queue = [start_point]

    Stream.iterate(0, &amp;(&amp;1 + 1))
    |> Enum.reduce_while({seen, queue}, fn _, {seen, queue} ->
      if queue == [] do
        {:halt, seen}
      else
        [{r, c, dr, dc} | queue] = queue



        r = r + dr
        c = c + dc

        char = Map.get(grid, {r, c}, nil)

        keep_moving = char == "." or (char == "-" and dc != 0) or (char == "|" and dr != 0)

        cond do
          char == nil ->
            {:cont, {seen, queue}}

          keep_moving ->
            {seen, queue} =
              if not MapSet.member?(seen, {r, c, dr, dc}) do
                queue = [{r, c, dr, dc} | queue]
                seen = MapSet.put(seen, {r, c, dr, dc})
                {seen, queue}
              else
                {seen, queue}
              end

            {:cont, {seen, queue}}

          char == "/" ->
            {dr, dc} = {-dc, -dr}

            {seen, queue} =
              if not MapSet.member?(seen, {r, c, dr, dc}) do
                queue = [{r, c, dr, dc} | queue]
                seen = MapSet.put(seen, {r, c, dr, dc})
                {seen, queue}
              else
                {seen, queue}
              end

            {:cont, {seen, queue}}

          char == "\\" ->
            {dr, dc} = {dc, dr}

            {seen, queue} =
              if not MapSet.member?(seen, {r, c, dr, dc}) do
                queue = [{r, c, dr, dc} | queue]
                seen = MapSet.put(seen, {r, c, dr, dc})
                {seen, queue}
              else
                {seen, queue}
              end

            {:cont, {seen, queue}}

          true ->
            directions = if char == "|", do: [{1, 0}, {-1, 0}], else: [{0, 1}, {0, -1}]

            {seen, queue} =
              Enum.reduce(directions, {seen, queue}, fn {dn, dc}, {seen_acc, q_acc} ->
                state = {r, c, dn, dc}

                if state in seen_acc do
                  {seen_acc, q_acc}
                else
                  {MapSet.put(seen_acc, state), q_acc ++ [state]}
                end
              end)

            {:cont, {seen, queue}}
        end
      end
    end)
  end
end

Part 01

Solver2.solve(grid, {0, -1, 0, 1}) 
|> Enum.map(fn {r, c, _, _} -> {r, c} end) 
|> Enum.uniq()
|> Enum.count()

Part 02

max_row = grid |> Map.keys() |> Enum.max_by(fn {r, _c} -> r end) |> elem(0)
max_col = grid |> Map.keys() |> Enum.max_by(fn {_r, c} -> c end) |> elem(1)

get_border_coords = fn max_row, max_col ->
  top = for c <- 0..max_col, do: {0, c, 1, 0}
  bottom = for c <- 0..max_col, do: {max_row, c, -1, 0}
  left = for r <- 1..(max_row - 1), do: {r, 0, 0, 1}
  right = for r <- 1..(max_row - 1), do: {r, max_col, 0, -1}

  top ++ bottom ++ left ++ right
end

coords = get_border_coords.(max_row, max_col)

Enum.reduce(coords, 0, fn coord, total ->
  current =
    Solver2.solve(grid, coord)
    |> Enum.map(fn {r, c, _, _} -> {r, c} end)
    |> Enum.uniq()
    |> Enum.count()

  max(current, total)
end)