Skip to content

Commit c1e4f0c

Browse files
committed
more catch up
1 parent d6ff846 commit c1e4f0c

File tree

5 files changed

+335
-0
lines changed

5 files changed

+335
-0
lines changed

2024/ruby/Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
66

77
gem "priority_queue_cxx"
88
gem "parallel"
9+
gem "ruby-progressbar"
910
gem "matrix"
1011

1112
gem "rake"

2024/ruby/Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ GEM
5151
prism (>= 1.2, < 2.0)
5252
rbs (>= 3, < 4)
5353
sorbet-runtime (>= 0.5.10782)
54+
ruby-progressbar (1.13.0)
5455
sorbet-runtime (0.5.11670)
5556
stringio (3.1.2)
5657
yard (0.9.37)
@@ -73,6 +74,7 @@ DEPENDENCIES
7374
rake
7475
rspec
7576
ruby-lsp
77+
ruby-progressbar
7678
yard-doctest
7779

7880
BUNDLED WITH

2024/ruby/day17.rb

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
require_relative 'day'
2+
require 'parallel'
3+
4+
class Day17 < Day # >
5+
6+
# @example
7+
# day.part1 #=> '4,6,3,5,6,3,5,2,1,0'
8+
def part1
9+
computer = ChronospatialComputer.new(input)
10+
computer.run
11+
computer.output.join(',')
12+
end
13+
14+
# @example
15+
# day = new(EXAMPLE_PART2)
16+
# day.part2 #=> 117440
17+
def part2
18+
quiney_inputs = Thread::Queue.new
19+
n = 0
20+
every = 1_000_000
21+
while quiney_inputs.empty?
22+
Parallel.map(n..n+every, in_threads: 4, progress: "Trying #{n}..#{n+every}") { |value|
23+
computer = ChronospatialComputer.new(input)
24+
computer.registers[:a] = value
25+
computer.run
26+
if computer.output == computer.program
27+
quiney_inputs << value
28+
raise Parallel::Break
29+
end
30+
}
31+
n += every
32+
end
33+
results = []
34+
results << quiney_inputs.pop until quiney_inputs.empty?
35+
results.min
36+
end
37+
38+
EXAMPLE_INPUT = File.read("../inputs/day17-example-input.txt")
39+
EXAMPLE_PART2 = <<~EXAMPLE
40+
Register A: 2024
41+
Register B: 0
42+
Register C: 0
43+
44+
Program: 0,3,5,4,3,0
45+
EXAMPLE
46+
end
47+
48+
class ChronospatialComputer
49+
attr_reader :input, :registers, :program, :output, :state
50+
# @example
51+
# computer = new(Day17::EXAMPLE_INPUT)
52+
# computer.registers #=> {a: 729, b: 0, c: 0}
53+
# computer.program #=> [0,1,5,4,3,0]
54+
def initialize(input='')
55+
@input = input
56+
@registers, @program = parse(input)
57+
@instruction_pointer = 0
58+
@output = []
59+
@state = :initialized
60+
end
61+
62+
# @example
63+
# computer = new('')
64+
# computer.parse(Day17::EXAMPLE_INPUT) #=> [{a: 729,b: 0,c: 0},[0,1,5,4,3,0]]
65+
def parse(input)
66+
input_numbers = input.scan(/\d+/).map(&:to_i)
67+
[
68+
{ a: input_numbers[0], b: input_numbers[1], c: input_numbers[2] },
69+
input_numbers[3..]
70+
]
71+
end
72+
73+
OPCODES = {
74+
0 => :adv,
75+
1 => :bxl,
76+
2 => :bst,
77+
3 => :jnz,
78+
4 => :bxc,
79+
5 => :out,
80+
6 => :bdv,
81+
7 => :cdv,
82+
}
83+
OPCODES.default_proc = ->(h,k){ raise "unknown opcode: #{k}" }
84+
85+
# @example If register C contains 9, the program 2,6 would set register B to 1.
86+
# computer = new('0 0 9 2 6')
87+
# computer.run
88+
# computer.registers[:b] #=> 1
89+
#
90+
# @example If register A contains 10, the program 5,0,5,1,5,4 would output 0,1,2.
91+
# computer = new('10 0 0 5 0 5 1 5 4')
92+
# computer.run
93+
# computer.output #=> [0,1,2]
94+
#
95+
# @example If register A contains 2024, the program 0,1,5,4,3,0 would output 4,2,5,6,7,7,7,7,3,1,0 and leave 0 in register A.
96+
# computer = new('2024 0 0 0 1 5 4 3 0')
97+
# computer.run
98+
# computer.output #=> [4,2,5,6,7,7,7,7,3,1,0]
99+
# computer.registers[:a] #=> 0
100+
#
101+
# @example If register B contains 29, the program 1,7 would set register B to 26.
102+
# computer = new('0 29 0 1 7')
103+
# computer.run
104+
# computer.registers[:b] #=> 26
105+
#
106+
# @example If register B contains 2024 and register C contains 43690, the program 4,0 would set register B to 44354.
107+
# computer = new('0 2024 43690 4 0')
108+
# computer.run
109+
# computer.registers[:b] #=> 44354
110+
def run
111+
@state = :running
112+
while @state != :halted
113+
if @instruction_pointer < 0 || @instruction_pointer >= @program.length
114+
@state = :halted
115+
break
116+
else
117+
opcode, operand = @program[@instruction_pointer, 2].tap{ puts _1.inspect if ENV['DEBUG'] }
118+
send(OPCODES.fetch(opcode), operand)
119+
end
120+
puts @output.inspect if ENV['DEBUG']
121+
end
122+
end
123+
124+
def decompile
125+
@decompiled ||=
126+
@program.each_slice(2).map{ |opcode, operand|
127+
"#{OPCODES.fetch(opcode)}(#{operand})"
128+
}.join("\n")
129+
end
130+
131+
def combo_value(combo)
132+
case combo
133+
when 0,1,2,3 ; combo
134+
when 4 ; @registers.fetch(:a)
135+
when 5 ; @registers.fetch(:b)
136+
when 6 ; @registers.fetch(:c)
137+
when 7 ; raise "reserved: should not have appeared in a valid program (...yet?)"
138+
end
139+
end
140+
141+
# @example with low combo (treat as literal)
142+
# computer = new('')
143+
# computer.registers[:a] = 17
144+
# computer.adv(2)
145+
# computer.registers[:a] #=> 4
146+
# @example with high combo (use value in register)
147+
# computer = new('')
148+
# computer.registers[:a] = 17
149+
# computer.registers[:b] = 2
150+
# computer.adv(5)
151+
# computer.registers[:a] #=> 4
152+
def adv(combo)
153+
@registers[:a] = ( @registers.fetch(:a) / 2**combo_value(combo) ).truncate
154+
@instruction_pointer += 2
155+
end
156+
157+
# @example
158+
# computer = new
159+
# computer.registers[:b] = 1
160+
# computer.bxl(2)
161+
# computer.registers[:b] #=> 3
162+
def bxl(literal)
163+
@registers[:b] = @registers[:b] ^ literal
164+
@instruction_pointer += 2
165+
end
166+
167+
def bst(combo)
168+
@registers[:b] = combo_value(combo) % 8
169+
@instruction_pointer += 2
170+
end
171+
172+
def jnz(literal)
173+
if @registers[:a].zero?
174+
@instruction_pointer += 2
175+
else
176+
@instruction_pointer = literal
177+
end
178+
end
179+
180+
def bxc(_ignored)
181+
@registers[:b] = @registers[:b] ^ @registers[:c]
182+
@instruction_pointer += 2
183+
end
184+
185+
def out(combo)
186+
@output << combo_value(combo) % 8
187+
@instruction_pointer += 2
188+
end
189+
190+
def bdv(combo)
191+
@registers[:b] = ( @registers.fetch(:a) / 2**combo_value(combo) ).truncate
192+
@instruction_pointer += 2
193+
end
194+
195+
def cdv(combo)
196+
@registers[:c] = ( @registers.fetch(:a) / 2**combo_value(combo) ).truncate
197+
@instruction_pointer += 2
198+
end
199+
end

2024/ruby/day18.rb

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
require_relative 'day'
2+
require 'fc' # FastContainers::PriorityQueue
3+
4+
class Day18 < Day # >
5+
attr_reader :memory_space_size, :start, :goal, :incoming_byte_positions, :first_bytes
6+
7+
def initialize(input = nil, memory_space_size: 70, first_bytes: 1024)
8+
super(input)
9+
@memory_space_size = memory_space_size
10+
@first_bytes = first_bytes
11+
@start = [0, 0]
12+
@goal = [@memory_space_size, @memory_space_size]
13+
@incoming_byte_positions =
14+
@input
15+
.split("\n")
16+
.map { |line| line.scan(/(\d+)/).flatten.map(&:to_i) }
17+
end
18+
19+
# @example
20+
# day = new(EXAMPLE_INPUT, memory_space_size: 6, first_bytes: 12)
21+
# day.part1 #=> 22
22+
def part1
23+
find_shortest_path_around_corrupted_memory(
24+
Set.new(incoming_byte_positions.take(first_bytes))
25+
).count - 1
26+
end
27+
28+
# @example
29+
# day = new(EXAMPLE_INPUT, memory_space_size: 6, first_bytes: 12)
30+
# day.part2 #=> "6,1"
31+
def part2
32+
low = first_bytes
33+
high = incoming_byte_positions.size
34+
result = nil
35+
36+
while low <= high
37+
mid = (low + high) / 2
38+
path_to_exit = find_shortest_path_around_corrupted_memory(
39+
Set.new(incoming_byte_positions.take(mid))
40+
)
41+
42+
if path_to_exit.empty?
43+
result = mid
44+
high = mid - 1
45+
else
46+
low = mid + 1
47+
end
48+
end
49+
50+
incoming_byte_positions[result - 1].join(",")
51+
end
52+
53+
def find_shortest_path_around_corrupted_memory(corrupted_memory)
54+
backsteps = Hash.new
55+
backsteps[start] = nil
56+
57+
costs = Hash.new(Float::INFINITY)
58+
costs[start] = 0 # we start here, though, so it's cheap
59+
60+
survey_queue = FastContainers::PriorityQueue.new(:min)
61+
survey_queue.push(start, 0)
62+
while !survey_queue.empty?
63+
check_pos = survey_queue.pop
64+
break if check_pos == goal
65+
66+
[ [-1, 0] , [1, 0], [0, -1], [0, 1] ]
67+
.map { |offset| [check_pos[0] + offset[0], check_pos[1] + offset[1]] }
68+
.select { |neighbor| neighbor.all? { _1.between?(0, @memory_space_size) } }
69+
.each do |neighbor|
70+
neighbor_cost =
71+
costs[check_pos] + (corrupted_memory.include?(neighbor) ? Float::INFINITY : 1)
72+
73+
if neighbor_cost < costs[neighbor]
74+
costs[neighbor] = neighbor_cost
75+
backsteps[neighbor] = check_pos
76+
77+
survey_queue.push(neighbor, neighbor_cost)
78+
end
79+
end
80+
end
81+
82+
path = []
83+
if backsteps.include?(goal)
84+
path.push(goal)
85+
step_backwards = backsteps[goal]
86+
while step_backwards
87+
path.push(step_backwards)
88+
step_backwards = backsteps[step_backwards]
89+
end
90+
end
91+
puts "\n" + debug(path, corrupted_memory) if ENV['DEBUG']
92+
path
93+
end
94+
95+
def debug(path, corrupted_memory)
96+
(0..@memory_space_size)
97+
.map { |row|
98+
(0..@memory_space_size)
99+
.map { |column|
100+
coords = [column, row]
101+
if path.include?(coords)
102+
"\e[41m\e[1mO\e[0m"
103+
elsif corrupted_memory.include?(coords)
104+
"\e[31m#\e[0m"
105+
else
106+
"\e[32m.\e[0m"
107+
end
108+
}.join("")
109+
}.join("\n")
110+
end
111+
112+
EXAMPLE_INPUT = File.read("../inputs/day18-example-input.txt")
113+
end

2024/ruby/day19.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
require_relative 'day'
2+
3+
class Day19 < Day # >
4+
5+
# @example
6+
# day.part1 #=> 6
7+
def part1
8+
towels, designs = input.split("\n\n")
9+
towel_matcher = Regexp.new(/\A(#{towels.gsub(", ", "|")})+\z/)
10+
desired_designs = designs.split("\n")
11+
desired_designs.count { |pattern| pattern.match?(towel_matcher) }
12+
end
13+
14+
# @example
15+
# day.part2 #=> 16
16+
def part2
17+
end
18+
19+
EXAMPLE_INPUT = File.read("../inputs/day19-example-input.txt")
20+
end

0 commit comments

Comments
 (0)