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

Day 8

d08.livemd

Day 8

Solution

defmodule D08 do
  def parse_input(input) do
    [moves | network] = input |> String.trim() |> String.split("\n", trim: true)

    network =
      Enum.map(network, fn node ->
        [node, paths] = String.split(node, " = ")
        [left, right] = paths |> String.trim("(") |> String.trim(")") |> String.split(", ")
        {node, %{left: left, right: right}}
      end)
      |> Enum.into(%{})

    {String.graphemes(moves), network}
  end

  def do_move(network, pos, "L"), do: network[pos][:left]
  def do_move(network, pos, "R"), do: network[pos][:right]

  def p1(moves, network) when is_list(moves) and is_map(network) do
    Stream.cycle(moves)
    |> Enum.reduce_while({"AAA", 0}, fn move, {pos, steps_taken} ->
      pos = do_move(network, pos, move)
      steps_taken = steps_taken + 1

      if pos == "ZZZ", do: {:halt, steps_taken}, else: {:cont, {pos, steps_taken}}
    end)
  end

  def p2(moves, network) when is_list(moves) and is_map(network) do
    ghost_steps(moves, network) |> lcm()
  end

  def ghost_steps(moves, network) do
    nodes = Map.keys(network)

    states =
      nodes
      |> Enum.filter(&String.ends_with?(&1, "A"))
      |> Enum.map(fn node -> {node, {node, 0}} end)
      |> Enum.into(%{})

    z_nodes = nodes |> Enum.filter(&String.ends_with?(&1, "Z")) |> MapSet.new()

    Stream.cycle(moves)
    |> Enum.reduce_while(
      {states, z_nodes, []},
      fn move, {states, z_nodes, steps} ->
        if map_size(states) == 0 do
          {:halt, steps}
        else
          states =
            Enum.map(states, fn {node, {pos, step}} ->
              pos = do_move(network, pos, move)
              {node, {pos, step + 1}}
            end)
            |> Enum.into(states)

          {states, steps} =
            Enum.filter(states, fn {_node, {pos, _}} -> pos in z_nodes end)
            |> Enum.reduce(
              {states, steps},
              fn {finished_node, {_pos, step}}, {states, steps} ->
                {Map.delete(states, finished_node), [step | steps]}
              end
            )

          {:cont, {states, z_nodes, steps}}
        end
      end
    )
  end

  def lcm(nums) do
    expr = "x=lcm(#{inspect(nums)}); println(x)"
    {res, 0} = System.cmd("julia", ["--eval", expr], stderr_to_stdout: true)
    String.trim(res) |> String.to_integer()
  end
end
{:module, D08, <<70, 79, 82, 49, 0, 0, 26, ...>>, {:lcm, 1}}

Tests

ExUnit.start(autorun: false)

defmodule D08Test do
  use ExUnit.Case, async: true

  @test_input1 D08.parse_input("""
               RL

               AAA = (BBB, CCC)
               BBB = (DDD, EEE)
               CCC = (ZZZ, GGG)
               DDD = (DDD, DDD)
               EEE = (EEE, EEE)
               GGG = (GGG, GGG)
               ZZZ = (ZZZ, ZZZ)
               """)

  @test_input2 D08.parse_input("""
               LLR

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

  @test_input3 D08.parse_input("""
               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)
               """)

  @input Path.join(__DIR__, "inputs/d08") |> File.read!() |> D08.parse_input()

  test "part 1 works" do
    {moves, network} = @test_input1
    assert D08.p1(moves, network) == 2

    {moves, network} = @test_input2
    assert D08.p1(moves, network) == 6

    {moves, network} = @input
    assert D08.p1(moves, network) == 19783
  end

  test "part 2 works" do
    {moves, network} = @test_input3
    assert D08.p2(moves, network) == 6

    {moves, network} = @input
    assert D08.p2(moves, network) == 9_177_460_370_549
  end
end

ExUnit.run()
..
Finished in 0.3 seconds (0.3s async, 0.00s sync)
2 tests, 0 failures

Randomized with seed 158468
%{excluded: 0, failures: 0, skipped: 0, total: 2}