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(&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()