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

Day 8: Haunted Wasteland

2023/day_08.livemd

Day 8: Haunted Wasteland

Mix.install([:kino, :nimble_parsec])

input = Kino.Input.textarea("Please paste your input:")

Part 1

Run in Livebook

https://adventofcode.com/2023/day/8

[instructions_str | nodes_str] =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)

defmodule Day08.Part1 do
  import NimbleParsec

  defparsec(
    :node,
    ascii_string([], 3)
    |> ignore(string(" = "))
    |> ignore(string("("))
    |> ascii_string([], 3)
    |> ignore(string(", "))
    |> ascii_string([], 3)
    |> ignore(string(")"))
  )
end

instructions = instructions_str |> String.codepoints()

node_map =
  nodes_str
  # with binary matching
  # |> Enum.map(fn <> <>
  #                  " = (" <> <> <> ", " <> <> <> ")" ->
  #   {node, {left, right}}
  # end)
  # with NimbleParsec
  |> Enum.map(fn node_str ->
    {:ok, [node, left, right], _, _, _, _} = node_str |> Day08.Part1.node()

    {node, {left, right}}
  end)
  |> Map.new()
Stream.cycle(instructions)
|> Enum.reduce_while({"AAA", 0}, fn
  _, {"ZZZ", step_count} ->
    {:halt, step_count}

  instruction, {current_node, step_count} ->
    instruction_tuple_index =
      case instruction do
        "L" -> 0
        "R" -> 1
      end

    {:cont, {node_map[current_node] |> elem(instruction_tuple_index), step_count + 1}}
end)

Part 2

https://adventofcode.com/2023/day/8#part2

gcd = fn
  a, 0, _gcd -> a
  a, b, gcd -> gcd.(b, rem(a, b), gcd)
end

start_nodes =
  node_map
  |> Map.keys()
  |> Enum.filter(fn node -> node |> String.at(-1) == "A" end)

start_nodes
|> Enum.map(fn start_node ->
  Stream.cycle(instructions)
  |> Enum.reduce_while({start_node, 0}, fn
    _, {<<_::binary-size(2)>> <> "Z", step_count} ->
      {:halt, step_count}

    instruction, {current_node, step_count} ->
      instruction_tuple_index =
        case instruction do
          "L" -> 0
          "R" -> 1
        end

      {:cont, {node_map[current_node] |> elem(instruction_tuple_index), step_count + 1}}
  end)
end)
|> Enum.reduce(fn x, acc ->
  # calc LCM
  a = max(x, acc)
  b = min(x, acc)

  div(a * b, gcd.(a, b, gcd))
end)