Created
December 18, 2017 10:52
-
-
Save sasa1977/7e0ebc2e36072bf90bcb139e02558f65 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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