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

Advent of Code 2024 - Day 17

2024/day-17.livemd

Advent of Code 2024 - Day 17

Mix.install([{:kino, github: "livebook-dev/kino"}])

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

Part 1

input = Kino.Input.read(kino_input)

defmodule Program do
  def parse_input(input) do
    [registers_str, program_str] = String.split(input, "\n\n", trim: true)

    registers =
      registers_str
      |> String.split("\n")
      |> Enum.reduce(%{}, fn register_str, acc ->
        [name, value] = String.split(register_str, ": ", trim: true)
        register_name = 
          case String.downcase(name) do
            "register a" -> :a
            "register b" -> :b
            "register c" -> :c
            _ -> String.to_atom(name)
          end
        
        Map.put(acc, register_name, String.to_integer(value))
      end)

    program_code =
      program_str
      |> String.replace(["Program: ", "\n"], "")
      |> String.split(",", trim: true)
      |> Enum.map(&String.to_integer/1)

    {program_code, registers}
  end

  def run_program({program_code, registers}) do
    run_program(program_code, registers, 0, [])
  end

  defp run_program(program_code, registers, ip, out) do
    if ip < length(program_code) do
      opcode = Enum.at(program_code, ip)
      operand = Enum.at(program_code, ip + 1)

      case opcode do
        0 ->
          numerator = Map.get(registers, :a)
          denominator = :math.pow(2, get_combo_value(operand, registers))
          result = trunc(numerator / denominator)

          run_program(program_code, Map.put(registers, :a, result), ip + 2, out)

        1 ->
          register_b_value = Map.get(registers, :b)
          result = Bitwise.bxor(register_b_value, operand)

          run_program(program_code, Map.put(registers, :b, result), ip + 2, out)

        2 ->
          combo_value = get_combo_value(operand, registers)
          result = rem(combo_value, 8)

          run_program(program_code, Map.put(registers, :b, result), ip + 2, out)

        3 ->
          if Map.get(registers, :a) == 0 do
            run_program(program_code, registers, ip + 2, out)
          else
            run_program(program_code, registers, operand, out)
          end

        4 ->
          register_b_value = Map.get(registers, :b)
          register_c_value = Map.get(registers, :c)
          result = Bitwise.bxor(register_b_value, register_c_value)

          run_program(program_code, Map.put(registers, :b, result), ip + 2, out)

        5 ->
          combo_value = get_combo_value(operand, registers)
          result = rem(combo_value, 8)

          run_program(program_code, registers, ip + 2, out ++ [result])

        6 ->
          numerator = Map.get(registers, :a)
          denominator = :math.pow(2, get_combo_value(operand, registers))
          result = trunc(numerator / denominator)

          run_program(program_code, Map.put(registers, :b, result), ip + 2, out)

        7 ->
          numerator = Map.get(registers, :a)
          denominator = :math.pow(2, get_combo_value(operand, registers))
          result = trunc(numerator / denominator)

          run_program(program_code, Map.put(registers, :c, result), ip + 2, out)

        _ -> run_program(program_code, registers, ip + 2, out)
      end
    else
      out
    end
  end
  
  def get_combo_value(operand, registers) do
    case operand do
      0 -> 0
      1 -> 1
      2 -> 2
      3 -> 3
      4 -> Map.get(registers, :a)
      5 -> Map.get(registers, :b)
      6 -> Map.get(registers, :c)
      _ -> -1
    end
  end
end

input
|> Program.parse_input()
|> Program.run_program()
|> Enum.join(",")

Part 2

input = Kino.Input.read(kino_input)

defmodule ProgramPart2 do
  def parse_input(input) do
    [registers_str, program_str] = String.split(input, "\n\n", trim: true)

    registers =
      registers_str
      |> String.split("\n")
      |> Enum.reduce(%{}, fn register_str, acc ->
        [name, value] = String.split(register_str, ": ", trim: true)
        register_name = 
          case String.downcase(name) do
            "register a" -> :a
            "register b" -> :b
            "register c" -> :c
            _ -> String.to_atom(name)
          end
        
        Map.put(acc, register_name, String.to_integer(value))
      end)

    program_code =
      program_str
      |> String.replace(["Program: ", "\n"], "")
      |> String.split(",", trim: true)
      |> Enum.map(&amp;String.to_integer/1)

    {program_code, registers}
  end

  def run_program({program_code, registers}) do
    run_program(program_code, registers, 0, [])
  end

  defp run_program(program_code, registers, ip, out) do
    if ip < length(program_code) do
      opcode = Enum.at(program_code, ip)
      operand = Enum.at(program_code, ip + 1)

      case opcode do
        0 ->
          numerator = Map.get(registers, :a)
          denominator = :math.pow(2, get_combo_value(operand, registers))
          result = trunc(numerator / denominator)

          run_program(program_code, Map.put(registers, :a, result), ip + 2, out)

        1 ->
          register_b_value = Map.get(registers, :b)
          result = Bitwise.bxor(register_b_value, operand)

          run_program(program_code, Map.put(registers, :b, result), ip + 2, out)

        2 ->
          combo_value = get_combo_value(operand, registers)
          result = rem(combo_value, 8)

          run_program(program_code, Map.put(registers, :b, result), ip + 2, out)

        3 ->
          if Map.get(registers, :a) == 0 do
            run_program(program_code, registers, ip + 2, out)
          else
            run_program(program_code, registers, operand, out)
          end

        4 ->
          register_b_value = Map.get(registers, :b)
          register_c_value = Map.get(registers, :c)
          result = Bitwise.bxor(register_b_value, register_c_value)

          run_program(program_code, Map.put(registers, :b, result), ip + 2, out)

        5 ->
          combo_value = get_combo_value(operand, registers)
          result = rem(combo_value, 8)

          run_program(program_code, registers, ip + 2, out ++ [result])

        6 ->
          numerator = Map.get(registers, :a)
          denominator = :math.pow(2, get_combo_value(operand, registers))
          result = trunc(numerator / denominator)

          run_program(program_code, Map.put(registers, :b, result), ip + 2, out)

        7 ->
          numerator = Map.get(registers, :a)
          denominator = :math.pow(2, get_combo_value(operand, registers))
          result = trunc(numerator / denominator)

          run_program(program_code, Map.put(registers, :c, result), ip + 2, out)

        _ -> run_program(program_code, registers, ip + 2, out)
      end
    else
      out
    end
  end
  
  def get_combo_value(operand, registers) do
    case operand do
      0 -> 0
      1 -> 1
      2 -> 2
      3 -> 3
      4 -> Map.get(registers, :a)
      5 -> Map.get(registers, :b)
      6 -> Map.get(registers, :c)
      _ -> -1
    end
  end

  def find_initial_value({program_code, registers}) do
    target_length = length(program_code)

    Enum.reduce((target_length - 1)..0//-1, 0, fn i, acc ->
      target = Enum.slice(program_code, i, target_length - i)

      find_a_value(i, acc, registers, program_code, target)
    end)
  end

  defp find_a_value(i, acc, registers, program_code, target, n \\ 0) do
    candidate_a = Bitwise.bsl(n, i * 3) |> Bitwise.bor(acc)

    updated_registers = Map.put(registers, :a, candidate_a)

    result = run_program({program_code, updated_registers}) |> Enum.slice(i, length(target))

    if result == target do
      candidate_a
    else
      find_a_value(i, acc, registers, program_code, target, n + 1)
    end
  end
end

input
|> ProgramPart2.parse_input()
|> ProgramPart2.find_initial_value()