Skip to content

Instantly share code, notes, and snippets.

@sasa1977
Created December 18, 2017 10:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sasa1977/7e0ebc2e36072bf90bcb139e02558f65 to your computer and use it in GitHub Desktop.
Save sasa1977/7e0ebc2e36072bf90bcb139e02558f65 to your computer and use it in GitHub Desktop.
defmodule Day18 do
defmodule Machine.Core do
def new(instructions), do:
%{
registers: %{},
position: 0,
instructions: instructions |> Enum.to_list() |> :array.from_list(fixed: true)
}
def done?(machine), do:
machine.position < 0 or machine.position >= :array.size(machine.instructions)
def current_instruction(machine), do:
:array.get(machine.position, machine.instructions)
def interpret(machine, {:set, [name, arg]}), do:
machine |> update_register(name, &%{&1 | value: value(machine, arg)}) |> next_instruction()
def interpret(machine, {:add, [name, arg]}), do:
machine |> update_register(name, &%{&1 | value: &1.value + value(machine, arg)}) |> next_instruction()
def interpret(machine, {:mul, [name, arg]}), do:
machine |> update_register(name, &%{&1 | value: &1.value * value(machine, arg)}) |> next_instruction()
def interpret(machine, {:mod, [name, arg]}), do:
machine |> update_register(name, &%{&1 | value: rem(&1.value, value(machine, arg))}) |> next_instruction()
def interpret(machine, {:jgz, [x, y]}) do
if value(machine, x) > 0, do: jump(machine, value(machine, y)), else: next_instruction(machine)
end
def update_register(machine, name, updater), do:
put_in(machine.registers[name], updater.(register(machine, name)))
def value(_machine, value) when is_integer(value), do: value
def value(machine, name) when is_binary(name), do: register(machine, name).value
def register(machine, name), do: Map.get(machine.registers, name, %{value: 0})
def next_instruction(machine), do: jump(machine, 1)
defp jump(machine, offset), do: update_in(machine.position, &(&1 + offset))
end
defmodule Machine.Part1 do
alias Day18.Machine
def new(instructions), do:
instructions |> Machine.Core.new() |> Map.merge(%{recovered: nil})
def done?(machine), do:
Machine.Core.done?(machine) or not is_nil(machine.recovered)
def output(machine), do: machine.recovered
def next(machine), do:
interpret(machine, Machine.Core.current_instruction(machine))
defp interpret(machine, {:snd, [arg]}), do:
machine
|> Machine.Core.update_register(arg, &Map.put(&1, :last_played, Machine.Core.value(machine, arg)))
|> Machine.Core.next_instruction()
defp interpret(machine, {:rcv, [name]}), do:
machine
|> Map.put(:recovered, recover(Machine.Core.register(machine, name)))
|> Machine.Core.next_instruction()
defp interpret(machine, other), do:
Machine.Core.interpret(machine, other)
defp recover(%{value: value, last_played: last_played}) when value > 0, do: last_played
defp recover(_other), do: nil
end
defmodule Machine.Part2 do
alias Day18.Machine
def new(instructions), do:
{new_process(instructions, 0), new_process(instructions, 1)}
def done?({p0, p1}), do:
(Machine.Core.done?(p0) and Machine.Core.done?(p1)) or (waiting?(p0) and waiting?(p1))
def output({_p0, p1}), do: p1.sent_count
def next({p0, p1}) do
{next_p0, next_p1} = p0 |> step_process() |> transfer_message(p1)
next_p1 |> step_process() |> transfer_message(next_p0)
end
defp new_process(instructions, pid), do:
instructions
|> Machine.Core.new()
|> Machine.Core.update_register("p", &%{&1 | value: pid})
|> Map.merge(%{queue: :queue.new(), sent: nil, sent_count: 0})
defp transfer_message(%{sent: nil} = p0, p1), do: {p0, p1}
defp transfer_message(%{sent: sent} = p0, p1), do:
{put_in(p0.sent, nil), update_in(p1.queue, &:queue.in(sent, &1))}
defp step_process(process) do
if Machine.Core.done?(process),
do: process,
else: interpret(process, Machine.Core.current_instruction(process))
end
defp interpret(process, {:snd, [arg]}) do
value = Machine.Core.value(process, arg)
%{process | sent: value, sent_count: process.sent_count + 1} |> Machine.Core.next_instruction()
end
defp interpret(process, {:rcv, [arg]}) do
case :queue.out(process.queue) do
{:empty, _queue} -> process
{{:value, value}, queue} ->
%{process | queue: queue}
|> Machine.Core.update_register(arg, &%{&1 | value: value})
|> Machine.Core.next_instruction()
end
end
defp interpret(process, other), do:
Machine.Core.interpret(process, other)
defp waiting?(process), do:
match?({:rcv, _}, Machine.Core.current_instruction(process)) and :queue.peek(process.queue) == :empty
end
def run_machine(machine_mod), do:
instructions()
|> machine_mod.new()
|> Stream.iterate(&machine_mod.next/1)
|> Stream.drop_while(&(not machine_mod.done?(&1)))
|> Enum.take(1)
|> hd()
|> machine_mod.output()
defp instructions(), do:
"input.txt"
|> File.stream!()
|> Stream.map(&String.trim/1)
|> Enum.map(&parse_instruction/1)
defp parse_instruction(instruction) do
[instruction | args] = String.split(instruction, " ")
{String.to_atom(instruction), Enum.map(args, &parse_arg/1)}
end
defp parse_arg(arg) do
case Integer.parse(arg) do
:error -> arg
{value, ""} -> value
end
end
end
Day18.run_machine(Day18.Machine.Part1) |> IO.inspect
Day18.run_machine(Day18.Machine.Part2) |> IO.inspect
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment