Powered by AppSignal & Oban Pro

Day 17

2024/day17.livemd

Day 17

Mix.install([:kino_aoc])

Section

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2024", "17", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION"))
{:ok,
 "Register A: 66245665\nRegister B: 0\nRegister C: 0\n\nProgram: 2,4,1,7,7,5,1,7,4,6,0,3,5,5,3,0"}
defmodule Computer do
  @instructions ~w[adv bxl bst jnz bxc out bdv cdv]a
                |> Enum.with_index()
                |> Map.new(fn {name, ins} -> {ins, name} end)

  def load(opcodes) do
    opcodes
    |> Enum.chunk_every(2)
    |> Enum.map(fn [opcode, op] ->
      {@instructions[opcode], op}
    end)
    |> List.to_tuple()
  end

  def process(program, reg) do
    reg = Map.merge(%{a: 0, b: 0, c: 0}, Map.new(reg))
    do_process(program, 0, reg, [])
  end

  defp do_process(program, idx, _reg, out) when idx >= tuple_size(program) do
    Enum.reverse(out)
  end

  defp do_process(program, idx, reg, out) do
    ins = elem(program, idx)

    case compute(ins, reg) do
      new_reg when is_map(new_reg) ->
        do_process(program, idx + 1, new_reg, out)

      {:out, op} ->
        do_process(program, idx + 1, reg, [op | out])

      {:jump, op} ->
        do_process(program, op, reg, out)
    end
  end

  def compute({:adv, op}, reg),
    do: %{reg | a: div(reg.a, 2 ** combo(op, reg))}

  def compute({:bdv, op}, reg),
    do: %{reg | b: div(reg.a, 2 ** combo(op, reg))}

  def compute({:cdv, op}, reg),
    do: %{reg | c: div(reg.a, 2 ** combo(op, reg))}

  def compute({:bxl, op}, reg),
    do: %{reg | b: Bitwise.bxor(reg.b, op)}

  def compute({:bxc, _op}, reg),
    do: %{reg | b: Bitwise.bxor(reg.b, reg.c)}

  def compute({:bst, op}, reg),
    do: %{reg | b: Bitwise.band(combo(op, reg), 0b111)}

  def compute({:out, op}, reg),
    do: {:out, Bitwise.band(combo(op, reg), 0b111)}

  def compute({:jnz, op}, reg) do
    if reg.a != 0 do
      {:jump, op}
    else
      reg
    end
  end

  defp combo(v, _) when v in 0..3, do: v
  defp combo(4, %{a: a}), do: a
  defp combo(5, %{b: b}), do: b
  defp combo(6, %{c: c}), do: c
end
{:module, Computer, <<70, 79, 82, 49, 0, 0, 21, ...>>, {:combo, 2}}
[
  "Register A: " <> a,
  "Register B: " <> b,
  "Register C: " <> c,
  "Program: " <> raw_code
] = String.split(puzzle_input, "\n", trim: true)

reg = %{
  a: String.to_integer(a),
  b: String.to_integer(b),
  c: String.to_integer(c)
} |> dbg()

code =
  raw_code
  |> String.split(",")
  |> Enum.map(&amp;String.to_integer/1)

program = Computer.load(code)
%{a: 66245665, b: 0, c: 0}
{{:bst, 4}, {:bxl, 7}, {:cdv, 5}, {:bxl, 7}, {:bxc, 6}, {:adv, 3}, {:out, 5}, {:jnz, 0}}

Part 1

out = Computer.process(program, reg)
[1, 4, 6, 1, 6, 4, 3, 0, 3]
IO.puts(Enum.join(out, ","))
1,4,6,1,6,4,3,0,3
:ok

Part 2

defmodule Quine do
  def find(program, target) do
    do_find(program, Enum.reverse(target), Enum.to_list(0..7))
  end

  def do_find(_program, [], out), do: out

  def do_find(program, [n | rest], out) do
    potential =
      for a <- out,
          i <- 0..7,
          hd(Computer.process(program, a: a * 8 + i)) == n,
          do: a * 8 + i

    do_find(program, rest, potential)
  end
end
{:module, Quine, <<70, 79, 82, 49, 0, 0, 10, ...>>, {:do_find, 3}}
Quine.find(program, code) |> Enum.min()
265061364597659