146 lines
4.1 KiB
Elixir
146 lines
4.1 KiB
Elixir
|
#!/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()
|