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

Day 17

2024/day17.livemd

Day 17

Solution

defmodule State do
  defstruct [:program, :a, :b, :c, ptr: 0, output: []]
end
{:ok, contents} = File.read("#{__DIR__}/inputs/day17.txt")

{raw_registers, [raw_program]} =
  contents
  |> String.split("\n", trim: true)
  |> Enum.map(&String.split/1)
  |> Enum.map(&List.last/1)
  |> Enum.split(3)

[a, b, c] = Enum.map(raw_registers, &String.to_integer/1)

program =
  raw_program
  |> String.split(",")
  |> Enum.map(&String.to_integer/1)
  |> List.to_tuple()

start = %State{program: program, a: a, b: b, c: c}
defmodule Part1 do
  defp pow2(n), do: :math.pow(2, n) |> round()
  
  def execute(state) when state.ptr >= tuple_size(state.program) do
    state.output |> Enum.reverse() |> Enum.join(",")
  end

  def execute(state) do
    opcode = elem(state.program, state.ptr)
    operand = elem(state.program, state.ptr + 1)
    state = %{state | ptr: state.ptr + 2}

    combo =
      case operand do
        4 -> state.a
        5 -> state.b
        6 -> state.c
        7 -> :error
        _ -> operand
      end

    state_ =
      case opcode do
        0 -> %{state | a: div(state.a, pow2(combo))}
        1 -> %{state | b: Bitwise.bxor(state.b, operand)}
        2 -> %{state | b: rem(combo, 8)}
        3 -> if state.a == 0, do: state, else: %{state | ptr: operand}
        4 -> %{state | b: Bitwise.bxor(state.b, state.c)}
        5 -> %{state | output: [rem(combo, 8) | state.output]}
        6 -> %{state | b: div(state.a, pow2(combo))}
        7 -> %{state | c: div(state.a, pow2(combo))}
      end

    execute(state_)
  end
end

Part1.execute(start)
# All the digit functions operate in reverse.
defmodule Part2 do
  defp pow2(n), do: :math.pow(2, n) |> round()

  def oct2dec([]), do: 0
  def oct2dec([x | xs]), do: x + 8 * oct2dec(xs)

  # Hardcoded on input
  defp get_output(digits) do
    a = oct2dec(digits)
    b = a |> rem(8) |> Bitwise.bxor(5)
    c = div(a, pow2(b))
    b = b |> Bitwise.bxor(6) |> Bitwise.bxor(c)
    rem(b, 8)
  end

  defp extend_input(output, digits) do
    0..7
    |> Enum.map(&[&1 | digits])
    |> Enum.filter(&(get_output(&1) == output))
  end

  def extend_all_inputs(output, inputs) do
    Enum.flat_map(inputs, &extend_input(output, &1))
  end
end

part2 =
  program
  |> Tuple.to_list()
  |> Enum.reverse()
  |> Enum.reduce([[]], &Part2.extend_all_inputs/2)
  |> Enum.map(&Part2.oct2dec/1)
  |> Enum.min()
# Sanity check
Part1.execute(%{start | a: part2}) == raw_program