118 lines
2.4 KiB
Ruby
Executable file
118 lines
2.4 KiB
Ruby
Executable file
#!/usr/bin/env ruby
|
|
|
|
require "debug"
|
|
|
|
input = (ARGV.first.nil? ? DATA : ARGF)
|
|
.readlines(chomp: true)
|
|
.map(&:chars)
|
|
|
|
class PriorityQueue < Array
|
|
alias_method :extract_min, :shift
|
|
|
|
def <<(v)
|
|
super
|
|
sort_by!(&:last)
|
|
end
|
|
end
|
|
|
|
def unbounded?(graph, node)
|
|
node.first < 0 ||
|
|
node.last < 0 ||
|
|
node.first >= graph.size ||
|
|
node.last >= graph.first.size
|
|
end
|
|
|
|
def dijkstra(graph, source, target)
|
|
queue = PriorityQueue.new
|
|
dist = Hash.new
|
|
prev = Hash.new
|
|
|
|
queue << [source, 0]
|
|
dist[source] = 0
|
|
prev[source] = nil
|
|
|
|
until queue.empty?
|
|
node, picoseconds = queue.extract_min
|
|
|
|
[[0, 1], [0, -1], [1, 0], [-1, 0]]
|
|
.map { |dx, dy| [node.first + dx, node.last + dy] }
|
|
.reject { unbounded?(graph, _1) }
|
|
.each do |neighbor|
|
|
next if graph.dig(*neighbor) == "#"
|
|
|
|
if dist[neighbor].nil? || picoseconds + 1 < dist[neighbor]
|
|
dist[neighbor] = picoseconds + 1
|
|
prev[neighbor] = node
|
|
queue << [neighbor, picoseconds + 1]
|
|
end
|
|
end
|
|
end
|
|
|
|
path = Array.new
|
|
u = target
|
|
while prev[u]
|
|
path.unshift(u)
|
|
u = prev[u]
|
|
end
|
|
|
|
return dist, prev, path
|
|
end
|
|
|
|
def find_cheats(graph, dist, path, finish)
|
|
cheats = Hash.new
|
|
path.each do |node|
|
|
[[0, 2], [0, -2], [2, 0], [-2, 0]]
|
|
.map { |dx, dy| [node.first + dx, node.last + dy] }
|
|
.reject { unbounded?(graph, _1) }
|
|
.each do |neighbor|
|
|
next if graph.dig(*neighbor) == "#"
|
|
|
|
next if dist[node] >= dist[neighbor]
|
|
|
|
cheats[[node, neighbor]] = dist[neighbor] - dist[node] - 2
|
|
end
|
|
end
|
|
|
|
return cheats
|
|
end
|
|
|
|
def manhattan_distance(node1, node2)
|
|
(node1.first - node2.first).abs + (node1.last - node2.last).abs
|
|
end
|
|
|
|
def discover(char, graph)
|
|
(0...graph.size).each do |x|
|
|
(0...graph.first.size).each do |y|
|
|
return [x, y] if char == graph.dig(x, y)
|
|
end
|
|
end
|
|
end
|
|
|
|
start = discover("S", input)
|
|
finish = discover("E", input)
|
|
|
|
dist, prev, path = dijkstra(input, start, finish)
|
|
|
|
p find_cheats(input, dist, [start] + path, finish)
|
|
.values.tally.select { _1 >= 100 }.values.sum
|
|
|
|
p dist.keys.combination(2)
|
|
.select { manhattan_distance(_1, _2) <= 20 }
|
|
.count { dist[_2] - dist[_1] >= manhattan_distance(_1, _2) + 100 }
|
|
|
|
__END__
|
|
###############
|
|
#...#...#.....#
|
|
#.#.#.#.#.###.#
|
|
#S#...#.#.#...#
|
|
#######.#.#.###
|
|
#######.#.#...#
|
|
#######.#.###.#
|
|
###..E#...#...#
|
|
###.#######.###
|
|
#...###...#...#
|
|
#.#####.#.###.#
|
|
#.#...#.#.#...#
|
|
#.#.#.#.#.#.###
|
|
#...#...#...###
|
|
###############
|