diff --git a/2024/day17.exs b/2024/day17.exs new file mode 100755 index 0000000..2b44939 --- /dev/null +++ b/2024/day17.exs @@ -0,0 +1,145 @@ +#!/usr/bin/env elixir +defmodule Day17 do + defmodule Computer do + defstruct a: 0, b: 0, c: 0, output: [], program: [] + end + + def part1(computer) do + process(computer.program, computer) + |> Map.fetch!(:output) + |> Enum.reverse() + |> Enum.join(",") + end + + def process([], computer), do: computer + + # adv div(A, 2^ combo operand) -> A + def process([0, operand | program], computer) do + value = div(computer.a, 2 ** combo(operand, computer)) + computer = %Computer{computer | a: value} + process(program, computer) + end + + # bxl B XOR literal operand -> B + def process([1, operand | program], computer) do + value = Bitwise.bxor(computer.b, operand) + computer = %Computer{computer | b: value} + process(program, computer) + end + + # bst combo operand mod 8 -> B (only 3 lowest bits) + def process([2, operand | program], computer) do + value = rem(combo(operand, computer), 8) + computer = %Computer{computer | b: value} + process(program, computer) + end + + # jnz noting if A is 0, otherwise set instruction to literal operand - don't inc by 2 if jump + def process([3, operand | program], computer) do + if computer.a == 0 do + process(program, computer) + else + program = Enum.drop(computer.program, operand) + process(program, computer) + end + end + + # bxc B XOR C -> B (ignore operand) + def process([4, _operand | program], computer) do + value = Bitwise.bxor(computer.b, computer.c) + computer = %Computer{computer | b: value} + process(program, computer) + end + + # out combo operand mod 8 + output (CSV) + def process([5, operand | program], computer) do + value = rem(combo(operand, computer), 8) + computer = %Computer{computer | output: [value | computer.output]} + process(program, computer) + end + + # bdv same as adv but store in B + def process([6, operand | program], computer) do + value = div(computer.a, 2 ** combo(operand, computer)) + computer = %Computer{computer | b: value} + process(program, computer) + end + + # cdv same as adv but store in C + def process([7, operand | program], computer) do + value = div(computer.a, 2 ** combo(operand, computer)) + computer = %Computer{computer | c: value} + process(program, computer) + end + + def combo(literal, _computer) when literal in [0, 1, 2, 3], do: literal + def combo(4, computer), do: computer.a + def combo(5, computer), do: computer.b + def combo(6, computer), do: computer.c + + def part2(computer) do + # TODO runs forever. Probably need to detect loops? + search_for_self(computer) + end + + def search_for_self(computer, a \\ 0) do + computer = %Computer{computer | a: a} + output = process(computer.program, computer).output |> Enum.reverse() + + if output == computer.program do + a + else + search_for_self(computer, a + 1) + end + end + + def input do + with [input_filename] <- System.argv(), + {:ok, input} <- File.read(input_filename) do + "Register A: " <> input = input + {a, input} = Integer.parse(input) + "\nRegister B: " <> input = input + {b, input} = Integer.parse(input) + "\nRegister C: " <> input = input + {c, input} = Integer.parse(input) + "\n\nProgram: " <> input = input + program = input |> String.split([",", "\n"], trim: true) |> Enum.map(&String.to_integer/1) + + %Computer{a: a, b: b, c: c, program: program} + else + _ -> :error + end + end + + ####################### + # HERE BE BOILERPLATE # + ####################### + + def run do + case input() do + :error -> print_usage() + input -> run_parts_with_timer(input) + end + end + + defp run_parts_with_timer(input) do + run_with_timer(1, fn -> part1(input) end) + run_with_timer(2, fn -> part2(input) end) + end + + defp run_with_timer(part, fun) do + {time, result} = :timer.tc(fun) + IO.puts("Part #{part} (completed in #{format_time(time)}):\n") + IO.puts("#{inspect(result)}\n") + end + + defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs" + defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms" + defp format_time(μsec), do: "#{μsec / 1_000_000}s" + + defp print_usage do + IO.puts("Usage: elixir day17.exs input_filename") + end +end + +Day17.run()