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

2023 day 8

2023/elixir/day-8.livemd

2023 day 8

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

Section

input = Kino.Input.textarea("input")
sample_input = """
LLR

AAA = (BBB, BBB)
BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)
"""

input = Kino.Input.read(input)

Part 1

defmodule Day8 do
  defmodule Part1 do
    def solve(input_str) do
      data = Day8.parse(input_str)

      data.dirs
      |> String.to_charlist()
      |> Stream.cycle()
      |> Stream.with_index(0)
      |> Enum.reduce_while("AAA", fn
        {_, step}, "ZZZ" ->
          {:halt, step}

        {dir, _step}, node ->
          next = Day8.move_one(node, dir, data.map)
          {:cont, next}
      end)
    end
  end

  def move_one(node, dir, map) do
    case dir do
      ?L -> map[node] |> elem(0)
      ?R -> map[node] |> elem(1)
    end
  end

  def parse(input_str) do
    [dir_str, map_str] = input_str |> String.split("\n\n", trim: true)

    %{dirs: dir_str, map: parse_map(map_str)}
  end

  defp parse_map(map_str) do
    map_str
    |> String.split("\n", trim: true)
    |> Enum.map(&parse_map_line/1)
    |> Enum.into(%{})
  end

  defp parse_map_line(<>) do
    {cur, {left, right}}
  end
end
Day8.Part1.solve(sample_input)
Day8.Part1.solve(input)

Part 2

sample_input2 = """
LR

11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)
"""
defmodule Day8.Part2 do
  def solve(input_str) do
    data = Day8.parse(input_str)

    data.map
    |> start_keys()
    |> Enum.map(&amp;simulate_one(&amp;1, data))
    |> Enum.reduce(fn a, b -> div(a * b, Integer.gcd(a, b)) end)
  end

  def simulate_one(start_node, data) do
    dir_len = byte_size(data.dirs)

    data.dirs
    |> String.to_charlist()
    |> Stream.cycle()
    |> Stream.with_index(0)
    |> Enum.reduce_while(
      {start_node, %{}},
      fn {dir, step}, {cur_node, visit} ->
        # this is at cycle point
        if is_map_key(visit, cur_node) and rem(step - visit[cur_node], dir_len) == 0 do
          {:halt,
           Enum.find_value(visit, fn {node, steps} -> String.ends_with?(node, "Z") &amp;&amp; steps end)}
        else
          visit = Map.put(visit, cur_node, step)
          next_node = Day8.move_one(cur_node, dir, data.map)
          {:cont, {next_node, visit}}
        end
      end
    )
  end

  defp start_keys(map) do
    map
    |> Map.keys()
    |> Enum.filter(&amp;String.ends_with?(&amp;1, "A"))
  end

  @deprecated "timeout solution"
  def simulate_all(start_keys, data) do
    data.dirs
    |> String.to_charlist()
    |> Stream.cycle()
    |> Stream.with_index(1)
    |> Enum.reduce_while(start_keys, fn {dir, step}, cur_nodes ->
      next_nodes = Enum.map(cur_nodes, &amp;Day8.move_one(&amp;1, dir, data.map))

      if Enum.all?(next_nodes, &amp;String.ends_with?(&amp;1, "Z")) do
        {:halt, step}
      else
        {:cont, next_nodes}
      end
    end)
  end
end
Day8.Part2.solve(input)