#!/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()