Skip to content

Commit eb4a32a

Browse files
committed
Day 20 - Puzzle 2
1 parent e648929 commit eb4a32a

File tree

2 files changed

+158
-3
lines changed

2 files changed

+158
-3
lines changed

lib/solutions/day_20.rb

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,147 @@ def part_one(input)
88
end
99

1010
def part_two(input)
11-
0
11+
sim = DayTwo.new(input)
12+
results = sim.run!
13+
14+
top_results = results.select { |k,v| k >= 100 }
15+
top_results.values.sum
16+
end
17+
18+
class DayTwo
19+
#Shamelessly stolen from day 16
20+
DIRECTIONS = [
21+
[0, -1], # North
22+
[1, 0], # East
23+
[0, 1], # South
24+
[-1, 0] # West
25+
]
26+
27+
TURN_COST = 1
28+
MOVE_COST = 1
29+
30+
def initialize(input)
31+
@grid = input.split("\n")
32+
@cols = @grid[0].size
33+
@rows = @grid.size
34+
35+
@start = find_cell('S')
36+
@finish = find_cell('E')
37+
end
38+
39+
def to_s
40+
@grid.map { |r| r.join('') }
41+
end
42+
43+
def n_step_reachable(x, y, n = 2)
44+
result = []
45+
first_step = neighbours(x, y)
46+
first_step.each do |dx, dy|
47+
second_step = neighbours(dx, dy)
48+
second_step.each do |ddx, ddy|
49+
## ddx,ddy is reachable in 2 steps
50+
result << [ddx, ddy] if @grid[ddy][ddx] != '#'
51+
end
52+
end
53+
result
54+
end
55+
56+
def run!
57+
distances_start = bfs_distances(@start)
58+
distances_finish = bfs_distances(@finish)
59+
60+
normal_time = distances_start[@finish[1]][@finish[0]]
61+
return 0 if normal_time == Float::INFINITY
62+
63+
# Get only cells that are reachable from both start and finish
64+
valid_cells = []
65+
# puts @grid.inspect
66+
(0...@rows).each do |y|
67+
(0...@cols).each do |x|
68+
next if @grid[y][x] == '#'
69+
if distances_start[y][x] != Float::INFINITY && distances_finish[y][x] != Float::INFINITY
70+
valid_cells << [x, y]
71+
end
72+
end
73+
end
74+
75+
# Index cells by row to speed lookups:
76+
valid_cells.sort_by!{|x,y| [x,y]}
77+
by_row = Hash.new{|h,k| h[k]=[]}
78+
valid_cells.each{|x,y| by_row[y] << [x,y]}
79+
80+
max_distance = 20
81+
savings_count = Hash.new(0)
82+
83+
valid_cells.each do |x, y|
84+
da = distances_start[y][x]
85+
86+
# Limit range of rows
87+
min_y = [0, y - max_distance].max
88+
max_y = [@rows - 1, y + max_distance].min
89+
90+
(min_y..max_y).each do |ry|
91+
by_row[ry].each do |dx, dy|
92+
m_dist = (x - dx).abs + (y - dy).abs
93+
next if m_dist > max_distance
94+
db = distances_finish[dy][dx]
95+
96+
cheat_time = da + m_dist + db
97+
savings = normal_time - cheat_time
98+
if savings > savings_count[[x,y,dx,dy]]
99+
savings_count[savings] += 1 if savings > 0
100+
end
101+
end
102+
end
103+
end
104+
105+
# puts savings_count.inspect
106+
savings_count
107+
end
108+
109+
def neighbours(x, y)
110+
[[x-1,y],[x+1,y],[x,y-1],[x,y+1]].select { |nx,ny| nx >= 0 && nx < @cols && ny >= 0 && ny < @rows }
111+
end
112+
113+
def bfs_distances(start)
114+
distances = Array.new(@rows) { Array.new(@cols, Float::INFINITY) }
115+
distances[start[1]][start[0]] = 0
116+
queue = [start]
117+
118+
head = 0
119+
while head < queue.size
120+
x, y = queue[head]
121+
head += 1
122+
123+
neighbours(x, y).each do |dx, dy|
124+
next if @grid[dy][dx] == '#'
125+
next if distances[dy][dx] < Float::INFINITY
126+
127+
distances[dy][dx] = distances[y][x] + 1
128+
queue << [dx, dy]
129+
end
130+
end
131+
132+
distances
133+
end
134+
135+
def can_move?(x, y)
136+
return false if x < 0 || x >= @cols
137+
return false if y < 0 || y >= @rows
138+
return false if @grid[y][x] == '#'
139+
true
140+
end
141+
142+
143+
def find_cell(ch)
144+
@grid.each_with_index do |row, y|
145+
x = row.index(ch)
146+
if x
147+
return [x, y]
148+
end
149+
end
150+
nil
151+
end
12152
end
13153

14154
class DayOne

spec/solutions/day_20_spec.rb

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
describe '#part_one' do
88
it 'calculates the correct solutions for part one' do
9-
results = subject.part_one(input)
9+
results = Day20::DayOne.new(input).run!
1010

1111
expect(results[2]).to eq(14)
1212
expect(results[4]).to eq(14)
@@ -24,7 +24,22 @@
2424

2525
describe '#part_two' do
2626
it 'calculates the correct solutions for part two' do
27-
expect(subject.part_two(input)).to eq(0)
27+
results = Day20::DayTwo.new(input).run!
28+
29+
expect(results[50]).to eq(32)
30+
expect(results[52]).to eq(31)
31+
expect(results[54]).to eq(29)
32+
expect(results[56]).to eq(39)
33+
expect(results[58]).to eq(25)
34+
expect(results[60]).to eq(23)
35+
expect(results[62]).to eq(20)
36+
expect(results[64]).to eq(19)
37+
expect(results[66]).to eq(12)
38+
expect(results[68]).to eq(14)
39+
expect(results[70]).to eq(12)
40+
expect(results[72]).to eq(22)
41+
expect(results[74]).to eq(4)
42+
expect(results[76]).to eq(3)
2843
end
2944
end
3045
end

0 commit comments

Comments
 (0)