Powered by AppSignal & Oban Pro

Advent of Code 2023 Day 8

2023/day-08.livemd

Advent of Code 2023 Day 8

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

Prep

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2023", "8", System.fetch_env!("LB_AOC_SESSION"))
IO.puts(puzzle_input)
[instructions, "" | paths] = String.split(puzzle_input, "\n")
instructions =
  for <<instruction <- instructions>> do
    case instruction do
      ?L -> 0
      ?R -> 1
    end
  end
map =
  paths
  |> Map.new(fn <<from::binary-3, " = (", left::binary-3, ", ", right::binary-3, ")">> ->
    {from, {left, right}}
  end)
instructions_stream = Stream.cycle(instructions)

Part 1

instructions_stream
|> Enum.reduce_while({"AAA", 0}, fn instruction, {curr, steps} ->
  if curr == "ZZZ" do
    {:halt, {curr, steps}}
  else
    {:cont, {elem(map[curr], instruction), steps + 1}}
  end
end)
|> elem(1)

Part 2

ghost_initial_positions = map |> Map.keys() |> Enum.filter(&String.ends_with?(&1, "A"))
steps =
  ghost_initial_positions
  |> Task.async_stream(
    fn ghost ->
      instructions_stream
      |> Enum.reduce_while({ghost, 0}, fn instruction, {curr, steps} ->
        if String.ends_with?(curr, "Z") do
          {:halt, {curr, steps}}
        else
          {:cont, {elem(map[curr], instruction), steps + 1}}
        end
      end)
      |> elem(1)
    end,
    ordered: false
  )
  |> Enum.map(&elem(&1, 1))
gcd = fn
  a, b, recur when a < b ->
    recur.(b, a, recur)

  a, b, recur ->
    case rem(a, b) do
      0 -> b
      r -> recur.(b, r, recur)
    end
end
lcm = fn a, b ->
  div(a * b, gcd.(a, b, gcd))
end
steps |> Enum.reduce(lcm)

Verify that each ghost can goes back to its original position

tmp =
  for ghost <- ghost_initial_positions do
    instructions_stream
    |> Enum.reduce_while({ghost, 0, []}, fn instruction, {curr, steps, zettas} ->
      if String.ends_with?(curr, "Z") do
        if length(zettas) > 1 do
          {:halt, {curr, steps, [{curr, steps} | zettas]}}
        else
          {:cont, {elem(map[curr], instruction), steps + 1, [{curr, steps} | zettas]}}
        end
      else
        {:cont, {elem(map[curr], instruction), steps + 1, zettas}}
      end
    end)
    |> then(&{ghost, elem(&1, 2)})
  end
  |> Map.new()
l = length(instructions)
tmp
|> Map.values()
|> List.flatten()
|> Enum.all?(fn {_, v} ->
  rem(v, l) == 0
end)