Advent of Code 2020: Day 8 (Ruby solution)

Tasks of the 8th day of Advent of Code 2020 are about executing instructions from the input file. The instruction list is corrupted. In the first task, you have to detect the infinite loop, stop the algorithm in this place, and check its result so far. The second task is to repair this list by finding one wrong command and swapping them to the correct one.

I chose an objective approach and implemented InstructionProcessor and ProcessInstruction classes.

class InstructionProcessor
  class InfinityLoop < StandardError
    attr_reader :acc, :steps_sequence

    def initialize acc, steps_sequence
      @acc = acc
      @steps_sequence = steps_sequence
    end
  end

  def initialize
    @acc = 0
    @index = 0
  end

  def process_instructions instruction
    steps_sequence = []
    while @index < instruction.size
      raise InfinityLoop.new(@acc, steps_sequence) if steps_sequence.include? @index
      steps_sequence << @index
      step = instruction.get_step @index
      send step[:command], step[:value] if step
    end
    @acc
  end

  private

  def acc value
    @acc += value
    @index += 1
  end

  def jmp value
    @index += value
  end

  def nop value
    @index += 1
  end
end
class ProcessInstruction
  def initialize path
    @file = File.read('inputs/day8.txt')
    reload
  end

  def reload
    @steps = @file.lines.map do |line|
      args = line.split
      {command: args.first, value: args.last.to_i}
    end
  end

  def get_step index
    @steps[index]
  end

  def size
    @steps.size
  end

  def swap_command index
    step = @steps[index]
    if step[:command] == 'jmp'
      @steps[index][:command] = 'nop'
    elsif step[:command] == 'nop'
      @steps[index][:command] = 'jmp'
    end
  end
end

Having those classes implemented it is easy to solve both tasks:

instructions = ProcessInstruction.new('inputs/day8.txt')
retry_count = 0
begin
  res = InstructionProcessor.new.process_instructions(instructions)
  puts "Second task answer: #{res}"
rescue InstructionProcessor::InfinityLoop => e
  if retry_count == 0
    puts "First task answer: #{e.acc}"
    original_step_sequence = e.steps_sequence
  end
  instructions.reload
  retry_count += 1
  instructions.swap_command(original_step_sequence[original_step_sequence.size - retry_count])
  retry
end