[2019][ruby][5.x] refactor computer for day 5
diff --git a/2019/ruby/computer.rb b/2019/ruby/computer.rb
index cbec2d2..c0d0984 100644
--- a/2019/ruby/computer.rb
+++ b/2019/ruby/computer.rb
@@ -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
diff --git a/2019/ruby/day_05.rb b/2019/ruby/day_05.rb
index b6681a9..c1353b2 100644
--- a/2019/ruby/day_05.rb
+++ b/2019/ruby/day_05.rb
@@ -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"))