Skip to content

Commit

Permalink
Year 2015: Day 23
Browse files Browse the repository at this point in the history
  • Loading branch information
joshleaves committed Feb 19, 2024
1 parent 9862eca commit 0073b1e
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 47 additions & 0 deletions spec/year_2015/day_23_input
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions spec/year_2015/day_23_sample_one
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
inc a
jio a, +2
tpl a
inc a
28 changes: 28 additions & 0 deletions spec/year_2015/day_23_spec.rb
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions year_2015.md
Original file line number Diff line number Diff line change
Expand Up @@ -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".
67 changes: 67 additions & 0 deletions year_2015/day_23.rb
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 0073b1e

Please sign in to comment.