diff --git a/README.md b/README.md index b769fb1..5a88361 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Consistency is hard without proper goals to set a good (let's dare say "atomic") I'm also adding notes that may be useful if you're learning Ruby. Notes for solving: -* [2015, up to day 22](year_2015.md) +* [2015, up to day 23](year_2015.md) * [2023, up to day 03](year_2023.md) # How to use diff --git a/spec/year_2015/day_23_input b/spec/year_2015/day_23_input new file mode 100644 index 0000000..a2b735a --- /dev/null +++ b/spec/year_2015/day_23_input @@ -0,0 +1,47 @@ +jio a, +18 +inc a +tpl a +inc a +tpl a +tpl a +tpl a +inc a +tpl a +inc a +tpl a +inc a +inc a +tpl a +tpl a +tpl a +inc a +jmp +22 +tpl a +inc a +tpl a +inc a +inc a +tpl a +inc a +tpl a +inc a +inc a +tpl a +tpl a +inc a +inc a +tpl a +inc a +inc a +tpl a +inc a +inc a +tpl a +jio a, +8 +inc b +jie a, +4 +tpl a +inc a +jmp +2 +hlf a +jmp -7 diff --git a/spec/year_2015/day_23_sample_one b/spec/year_2015/day_23_sample_one new file mode 100644 index 0000000..c55ca77 --- /dev/null +++ b/spec/year_2015/day_23_sample_one @@ -0,0 +1,4 @@ +inc a +jio a, +2 +tpl a +inc a diff --git a/spec/year_2015/day_23_spec.rb b/spec/year_2015/day_23_spec.rb new file mode 100644 index 0000000..f10ad75 --- /dev/null +++ b/spec/year_2015/day_23_spec.rb @@ -0,0 +1,28 @@ +require 'year_2015/day_23' + +describe Year2015::Day23 do + context 'when Part 1' do + subject(:sample_one) do + described_class.new(File.read('spec/year_2015/day_23_sample_one')).program + end + + it 'gives a final result' do + sample_one.execute + expect(sample_one.registers['a']).to eq(2) + end + end + + context 'when Results' do + subject(:input_data) do + File.read('spec/year_2015/day_23_input') + end + + it 'correctly answers part 1' do + expect(described_class.new(input_data).part_one).to eq(307) + end + + it 'correctly answers part 2' do + expect(described_class.new(input_data).part_two).to eq(160) + end + end +end diff --git a/year_2015.md b/year_2015.md index 3e46a32..2e9f68f 100644 --- a/year_2015.md +++ b/year_2015.md @@ -367,3 +367,20 @@ Year2015::Day22 ["Any sufficiently advanced bruteforce is indistinguishable from Dijkstra's algorithm"](https://en.wikipedia.org/wiki/Clarke%27s_three_laws) As for this exercise, a key lesson is: make commits BEFORE you rewrite your code. In that occasion, rewriting my code made it unworkable, no matter how many times I tried, and I must have made the same input mistake many times because my second day results is always off by twenty, even when [carefully copying an algorithm that works](https://github.com/rHermes/adventofcode/blob/master/2015/22/y2015_d22_p02.py). I will leave it like that for now. + +## Day 23 + +``` +Year2015::Day23 + when Part 1 + gives a final result + when Results + correctly answers part 1 + correctly answers part 2 +``` + +We are not [Turing complete](https://en.wikipedia.org/wiki/Turing_completeness) yet, but learning to write a virtual machine is always a fun exercise to understand how computers work. + +As a small bonus on this exercise, instead of [re-interpreting each instruction as they come](https://github.com/rHermes/adventofcode/blob/master/2015/23/y2015_d23_p01.py), which can be costly on the long term, I used an approach similar to [Ahead-of-time compilation](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) to have all my instructions in ~~machine code~~ Ruby code and ready to reuse as needed. + +That said: make sure you ALWAYS read the exercise WORD. BY. WORD. Because if `jie` means "jump-if-even", it does not mean `jio` will mean "jump-if-odd". diff --git a/year_2015/day_23.rb b/year_2015/day_23.rb new file mode 100644 index 0000000..7ceb196 --- /dev/null +++ b/year_2015/day_23.rb @@ -0,0 +1,67 @@ +class Year2015 + class Day23 + attr_accessor :program + + class Program + attr_accessor :instructions, :pc, :registers + + def initialize(input_instructions) + @instructions = input_instructions.map do |instruction| + [instruction, interpret(instruction)] + end + end + + # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Style/Semicolon + def interpret(instruction) + case instruction + when /^(hlf|tpl|inc) ([ab])$/ + action = $1 + reg = $2 + case action + when 'hlf' + proc{ @pc += 1; @registers[reg] = @registers[reg] / 2 } + when 'tpl' + proc{ @pc += 1; @registers[reg] *= 3 } + when 'inc' + proc{ @pc += 1; @registers[reg] += 1 } + end + when /^jmp ([+-]?\d+)$/ + offset = $1.to_i + proc{ @pc += offset } + when /^ji([eo]) ([ab]), ([\+\-]\d+)$/ + even_or_one = $1 == 'e' + reg = $2 + offset = $3.to_i + proc do + move_offset = (even_or_one && @registers[reg].even?) || (!even_or_one && @registers[reg] == 1) + @pc += move_offset ? offset : 1 + end + else + raise "Invalid instruction: #{instruction}" + end + end + # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Style/Semicolon + + def execute(init_registers = { 'a' => 0, 'b' => 0 }) + @pc = 0 + @registers = init_registers + + instance_eval(&instructions[@pc].last) while instructions[@pc] + end + end + + def initialize(input_data) + @program = Program.new(input_data.chomp.split("\n")) + end + + def part_one + @program.execute + @program.registers['b'] + end + + def part_two + @program.execute('a' => 1, 'b' => 0) + @program.registers['b'] + end + end +end