aoc 2019 day 9
Setup
Mix.install([{:kino, "~> 0.5.1"}])
input = Kino.Input.textarea("")
init_state =
input
|> Kino.Input.read()
|> String.split(",", trim: true)
|> Enum.map(&String.to_integer/1)
|> Enum.with_index()
|> Map.new(fn {v, k} -> {k, v} end)
Computer
defmodule Computer.Operator do
defstruct [:params, :op_code]
def new(instruction) do
op_code = rem(instruction, 100)
params = get_parameters(instruction)
%__MODULE__{
op_code: op_code(op_code),
params: params
}
end
defp get_parameters(instruction) do
{
instruction |> div(100) |> rem(10),
instruction |> div(1_000) |> rem(10),
instruction |> div(10_000) |> rem(10)
}
end
defp op_code(1), do: :add
defp op_code(2), do: :mul
defp op_code(3), do: :read
defp op_code(4), do: :print
defp op_code(5), do: :jump_neq
defp op_code(6), do: :jump_eq
defp op_code(7), do: :comp_lt
defp op_code(8), do: :comp_eq
defp op_code(9), do: :adj_rb
defp op_code(99), do: :halt
end
defmodule Computer.Memory do
@addr_mode 0
@inc_mode 1
@relative_mode 2
defstruct memory: %{}, inputs: [], outputs: [], pointer: 0, relative_base: 0
def new(data: %__MODULE__{memory: map, inputs: prev_inputs, pointer: pointer}, inputs: inputs),
do: %__MODULE__{memory: map, inputs: prev_inputs ++ inputs, pointer: pointer}
def new(data: map, inputs: inputs) do
%__MODULE__{memory: map, inputs: inputs}
end
def get_instruction(%__MODULE__{memory: map, pointer: pointer}), do: map[pointer]
def get_value(%__MODULE__{memory: map, pointer: p}, offset, @inc_mode), do: map[p + offset]
def get_value(%__MODULE__{memory: map, pointer: p}, offset, @addr_mode),
do: map[map[p + offset]]
def get_value(%__MODULE__{memory: map, pointer: p, relative_base: rb}, offset, @relative_mode),
do: map[rb + map[p + offset]]
def put_value(%__MODULE__{memory: map, pointer: p} = s, offset, value, @addr_mode),
do: %{s | memory: Map.put(map, map[p + offset], value)}
def put_value(
%__MODULE__{memory: map, pointer: p, relative_base: rb} = s,
offset,
value,
@relative_mode
),
do: %{s | memory: Map.put(map, rb + map[p + offset], value)}
def append_output(memory, output), do: %{memory | outputs: [output | memory.outputs]}
def move_pointer(memory, next_ptr), do: %{memory | pointer: next_ptr}
def pop_input(%__MODULE__{inputs: [input | rest]} = memory),
do: {input, %{memory | inputs: rest}}
def put_inputs(memory, inputs), do: %{memory | inputs: memory.inputs ++ inputs}
def adjust_relative_base(memory, offset),
do: %{memory | relative_base: memory.relative_base + offset}
end
defmodule Computer do
alias Computer.{Memory, Operator}
@type mode :: :until_halt | :until_output
@spec init_state(map(), list()) :: %Memory{}
def init_state(data, inputs \\ []) do
Memory.new(data: data, inputs: inputs)
end
@spec process(%Memory{}, list(), keyword()) ::
{%Memory{}, :halt} | {%Memory{}, :hold} | {%Memory{}, list()}
def process(memory, inputs, opts \\ []) do
mode = Keyword.get(opts, :mode, :until_halt)
compute_fn =
case mode do
:until_halt -> &compute_until_halt/1
:until_output -> &compute_until_output/1
end
Memory.new(data: memory, inputs: inputs)
|> compute_fn.()
end
@spec compute_until_halt(%Memory{}) :: %Memory{}
def compute_until_halt(memory) do
Memory.get_instruction(memory)
|> Operator.new()
|> case do
%Operator{op_code: :halt} ->
memory
op ->
case move_operation(op, memory) do
{_memory, :hold} -> raise "error"
{memory, _outputs} -> memory
memory -> memory
end
|> compute_until_halt()
end
end
@spec compute_until_output(%Memory{}) ::
{%Memory{}, :halt} | {%Memory{}, :hold} | {%Memory{}, list()}
def compute_until_output(memory) do
Memory.get_instruction(memory)
|> Operator.new()
|> case do
%Operator{op_code: :halt} ->
{memory, :halt}
op ->
move_operation(op, memory)
|> case do
{memory, :hold} ->
{memory, :hold}
{memory, outputs} ->
{memory, outputs}
memory ->
compute_until_output(memory)
end
end
end
defp move_operation(%Operator{op_code: op_code, params: {p1, p2, p3}}, memory)
when op_code in [:add, :mul] do
v1 = Memory.get_value(memory, 1, p1)
v2 = Memory.get_value(memory, 2, p2)
res = calc(op_code).(v1, v2)
next_pointer = memory.pointer + 4
Memory.put_value(memory, 3, res, p3)
|> Memory.move_pointer(next_pointer)
end
defp move_operation(%Operator{op_code: :read}, %{inputs: []} = memory) do
{memory, :hold}
end
defp move_operation(%Operator{op_code: :read, params: {p1, _, _}}, memory) do
{input, memory} = Memory.pop_input(memory)
next_pointer = memory.pointer + 2
Memory.put_value(memory, 1, input, p1)
|> Memory.move_pointer(next_pointer)
end
defp move_operation(%Operator{op_code: :print, params: {p1, _, _}}, memory) do
output = Memory.get_value(memory, 1, p1)
next_pointer = memory.pointer + 2
Memory.append_output(memory, output)
|> Memory.move_pointer(next_pointer)
|> then(&{&1, &1.outputs})
end
defp move_operation(%Operator{op_code: :jump_neq, params: {p1, p2, _}}, memory) do
predicate = Memory.get_value(memory, 1, p1)
goto = Memory.get_value(memory, 2, p2)
next_pointer = if predicate != 0, do: goto, else: memory.pointer + 3
Memory.move_pointer(memory, next_pointer)
end
defp move_operation(%Operator{op_code: :jump_eq, params: {p1, p2, _}}, memory) do
predicate = Memory.get_value(memory, 1, p1)
goto = Memory.get_value(memory, 2, p2)
next_pointer = if predicate == 0, do: goto, else: memory.pointer + 3
Memory.move_pointer(memory, next_pointer)
end
defp move_operation(%Operator{op_code: :comp_lt, params: {p1, p2, p3}}, memory) do
v1 = Memory.get_value(memory, 1, p1)
v2 = Memory.get_value(memory, 2, p2)
value = if v1 < v2, do: 1, else: 0
next_pointer = memory.pointer + 4
Memory.put_value(memory, 3, value, p3)
|> Memory.move_pointer(next_pointer)
end
defp move_operation(%Operator{op_code: :comp_eq, params: {p1, p2, p3}}, memory) do
v1 = Memory.get_value(memory, 1, p1)
v2 = Memory.get_value(memory, 2, p2)
value = if v1 == v2, do: 1, else: 0
next_pointer = memory.pointer + 4
Memory.put_value(memory, 3, value, p3)
|> Memory.move_pointer(next_pointer)
end
defp move_operation(%Operator{op_code: :adj_rb, params: {p1, _, _}}, memory) do
v = Memory.get_value(memory, 1, p1)
Memory.adjust_relative_base(memory, v)
|> Memory.move_pointer(memory.pointer + 2)
end
defp calc(:add), do: &Kernel.+/2
defp calc(:mul), do: &Kernel.*/2
end
Part 1
Computer.process(init_state, [1], mode: :until_halt)
Part 2
Computer.process(init_state, [2], mode: :until_halt)