[2019][ruby][5.x] refactor computer for day 5

sorbet
Alpha Chen 5 years ago
parent 475ad99996
commit 897cc6c162

@ -1,60 +1,185 @@
OPCODES = {
1 => ->(m, a, b, c) { m[c] = m[a] + m[b] },
2 => ->(m, a, b, c) { m[c] = m[a] * m[b] },
99 => ->(*) { throw :halt },
1 => ->(m, _, _, a, b, c) { m[c] = m[a] + m[b] ; nil }, # add
2 => ->(m, _, _, a, b, c) { m[c] = m[a] * m[b] ; nil }, # multiply
3 => ->(m, i, _, a) { m[a] = i.gets.to_i ; nil }, # input
4 => ->(m, _, o, a) { o.puts(m[a]) ; nil }, # output
5 => ->(m, _, _, a, b) { m[a].nonzero? ? m[b] : nil }, # jump-if-true
6 => ->(m, _, _, a, b) { m[a].zero? ? m[b] : nil }, # jump-if-false
7 => ->(m, _, _, a, b, c) { m[c] = (m[a] < m[b]) ? 1 : 0; nil }, # less than
8 => ->(m, _, _, a, b, c) { m[c] = (m[a] == m[b]) ? 1 : 0; nil }, # equals
99 => ->(*) { throw :halt },
}
class Parameter
def self.from(value)
case value
when Parameter
value
when Integer
new(0, value)
else
raise "unexpected value: #{value}"
end
end
attr_reader :value
def initialize(mode, value)
raise "unexpected mode: #{mode.inspect}" unless [0, 1].include?(mode)
@mode, @value = mode, value
end
def position_mode?
@mode.zero?
end
def immediate_mode?
@mode.nonzero?
end
end
class Computer
def self.from(input)
new(input.split(?,).map(&:to_i))
end
def initialize(program)
@memory = program
attr_reader :input, :output
def initialize(program, input=STDIN, output=STDOUT)
@memory, @input, @output = program.dup, input, output
@pc = 0
end
def run
each.inject(nil) {|_,i| i }
def run(input=STDIN, output=STDOUT)
each(input, output).inject(nil) {|_,i| i }
end
def each
return enum_for(__method__) unless block_given?
def each(input, output)
return enum_for(__method__, input, output) unless block_given?
catch(:halt) do
loop do
opcode = OPCODES.fetch(@memory.fetch(@pc))
instruction = @memory[@pc].to_s.rjust(5, ?0)
opcode = OPCODES.fetch(instruction[-2..-1].to_i)
@pc += 1
n = opcode.arity - 1
args = (0...n).map {|i| @memory.fetch(@pc + i) }
n = opcode.arity - 3
args = (0...n).zip(instruction[0..2].reverse.chars.map(&:to_i)).map {|i, mode|
value = @memory[@pc + i]
Parameter.new(mode, value)
}
@pc += n
opcode.call(@memory, *args)
@pc = opcode.call(self, input, output, *args) || @pc
yield @memory
end
end
end
def [](parameter)
parameter = Parameter.from(parameter)
parameter.position_mode? ? @memory.fetch(parameter.value) : parameter.value
end
alias_method :fetch, :[]
def []=(parameter, value)
raise "writes should never be in immediate mode" if parameter.immediate_mode?
@memory[parameter.value] = value
end
end
require "minitest"
class TestComputer < Minitest::Test
def test_samples
c = Computer.from("1,9,10,3,2,3,11,0,99,30,40,50").each
c = Computer.from("1,9,10,3,2,3,11,0,99,30,40,50").each(StringIO.new, StringIO.new)
assert_equal 70, c.next.fetch(3)
assert_equal 3500, c.next.fetch(0)
assert_equal 70, c.next[3]
assert_equal 3500, c.next[0]
end
def test_more_samples
run = ->(p) { Computer.from(p).run }
assert_equal 2, run_program("1,0,0,0,99")[0]
assert_equal 6, run_program("2,3,0,3,99")[3]
assert_equal 9801, run_program("2,4,4,5,99,0")[5]
assert_equal 30, run_program("1,1,1,4,99,5,6,0,99")[0]
end
def test_parameter_modes
assert_equal 99, run_program("1002,4,3,4,33")[4]
assert_equal 99, run_program("1101,100,-1,4,0")[4]
end
def test_input_output
assert_equal 1, run_program("3,50,99", input: "1\n")[50]
output = StringIO.new
run_program("4,3,99,50", output: output)
assert_equal "50\n", output.string
end
def test_comparisons
# Using position mode, consider whether the input is equal to 8; output 1
# (if it is) or 0 (if it is not).
{ 7 => 0, 8 => 1, 9 => 0 }.each do |i, o|
c = Computer.from("3,9,8,9,10,9,4,9,99,-1,8")
output = StringIO.new
c.run(StringIO.new(i.to_s), output)
assert_equal "#{o}\n", output.string
end
# Using position mode, consider whether the input is less than 8; output 1
# (if it is) or 0 (if it is not).
{ 7 => 1, 8 => 0, 9 => 0 }.each do |i, o|
c = Computer.from("3,9,7,9,10,9,4,9,99,-1,8")
output = StringIO.new
c.run(StringIO.new(i.to_s), output)
assert_equal "#{o}\n", output.string
end
# Using immediate mode, consider whether the input is equal to 8; output 1
# (if it is) or 0 (if it is not).
{ 7 => 0, 8 => 1, 9 => 0 }.each do |i, o|
c = Computer.from("3,3,1108,-1,8,3,4,3,99")
output = StringIO.new
c.run(StringIO.new(i.to_s), output)
assert_equal "#{o}\n", output.string
end
# Using immediate mode, consider whether the input is less than 8; output 1
# (if it is) or 0 (if it is not).
{ 7 => 1, 8 => 0, 9 => 0 }.each do |i, o|
c = Computer.from("3,3,1107,-1,8,3,4,3,99")
output = StringIO.new
c.run(StringIO.new(i.to_s), output)
assert_equal "#{o}\n", output.string
end
end
def test_jumps
{ -1 => 1, 0 => 0, 1 => 1 }.each do |i, o|
c = Computer.from("3,12,6,12,15,1,13,14,13,4,13,99,-1,0,1,9")
output = StringIO.new
c.run(StringIO.new(i.to_s), output)
assert_equal "#{o}\n", output.string
c = Computer.from("3,3,1105,-1,9,1101,0,0,12,4,12,99,1")
output = StringIO.new
c.run(StringIO.new(i.to_s), output)
assert_equal "#{o}\n", output.string
end
end
private
assert_equal 2, run.call("1,0,0,0,99").fetch(0)
assert_equal 6, run.call("2,3,0,3,99").fetch(3)
assert_equal 9801, run.call("2,4,4,5,99,0").fetch(5)
assert_equal 30, run.call("1,1,1,4,99,5,6,0,99").fetch(0)
def run_program(p, input: "", output: StringIO.new)
Computer.from(p).run(
StringIO.new(input),
output,
)
end
end

@ -1,52 +1,5 @@
OPCODES = ARGF.read.split(?,).map(&:to_i)
require_relative "computer"
def run(memory)
pc = 0
loop do
instruction = memory[pc]
return if instruction == 99
p pc
a, b, c, opcode = instruction.to_s.rjust(5, ?0).scan(/^(0|1)(0|1)(0|1)(\d\d)$/)[0].map(&:to_i)
c = c.zero? ? memory[memory[pc+1]] : memory[pc+1]
b = b.zero? ? memory[memory[pc+2]] : memory[pc+2]
a = a.zero? ? memory[memory[pc+3]] : memory[pc+3]
case opcode
when 1
memory[memory[pc+3]] = c + b
pc += 4
when 2
memory[memory[pc+3]] = c * b
pc += 4
when 3
memory[memory[pc+1]] = 5
pc += 2
when 4
puts c
pc += 2
when 5
if c.nonzero?
pc = b
else
pc += 3
end
when 6
if c.zero?
pc = b
else
pc += 3
end
when 7
memory[memory[pc+3]] = (c < b) ? 1 : 0
pc += 4
when 8
memory[memory[pc+3]] = (c == b) ? 1 : 0
pc += 4
else
fail "unrecognized instruction: #{instruction}"
end
end
end
run(OPCODES)
program = ARGF.read.split(?,).map(&:to_i)
computer = Computer.new(program)
computer.run(StringIO.new("5\n"))

Loading…
Cancel
Save