lib/shortest_path/finder.rb
require 'fc'
module ShortestPath
class Finder
attr_reader :source, :destination
def initialize(source, destination)
@source, @destination = source, destination
@visited = {}
end
# Should return a map with accessible nodes and associated weight
# Example : { :a => 2, :b => 3 }
attr_accessor :ways_finder
def refresh_context( node, context)
{}
end
def ways(node, context={})
ways_finder.call node
end
def shortest_distances
@shortest_distances ||= {}
end
def previous
@previous ||= {}
end
# @context is a hash
# each node is related to an hash defining the context
# context is specific to the path solution between departure dans target node
#
def context
@context ||= {}
end
def search_heuristic(node)
shortest_distances[node]
end
def follow_way?(node, destination, weight, context={})
true
end
# timeout is in seconds
attr_accessor :timeout
attr_reader :begin_at, :end_at
def timeout?
timeout and (duration > timeout)
end
def duration
return nil unless begin_at
(end_at or Time.now) - begin_at
end
def visited?(node)
@visited[node]
end
def visit(node)
@visited[node] = true
end
def found?(node)
node == destination
end
def path_without_cache
@begin_at = Time.now
pq = ::FastContainers::PriorityQueue.new(:min)
# PQueue.new do |x,y|
# search_heuristic(x) < search_heuristic(y)
# end
pq.push(source, 0)
visit source
shortest_distances[source] = 0
not_found = !found?(source)
while pq.size != 0 && not_found
raise TimeoutError if timeout?
v = pq.pop
not_found = !found?(v)
visit v
weights = ways(v)
if weights
weights.keys.each do |w|
w_distance = shortest_distances[v] + weights[w]
if !visited?(w) and
weights[w] and
( shortest_distances[w].nil? || shortest_distances[w] > w_distance) and
follow_way?(v, w, weights[w])
shortest_distances[w] = w_distance
previous[w] = v
pq.push(w, search_heuristic(w) )
end
end
end
end
@end_at = Time.now
#puts previous.inspect
not_found ? [] : sorted_array
end
# def path_without_cache
# @begin_at = Time.now
# pq = PQueue.new do |x,y|
# search_heuristic(x) < search_heuristic(y)
# end
# pq.push( source)
# visit source
# shortest_distances[source] = 0
# context[source] = {}
# not_found = !found?(source)
# while pq.size != 0 && not_found
# raise TimeoutError if timeout?
# v = pq.pop
# not_found = !found?(v)
# visit v
# weights = ways(v, context[v])
# if weights
# weights.keys.each do |w|
# if !visited?(w) and
# weights[w] and
# ( shortest_distances[w].nil? || shortest_distances[w] > shortest_distances[v] + weights[w]) and
# follow_way?(v, w, weights[w], context[v])
# shortest_distances[w] = shortest_distances[v] + weights[w]
# previous[w] = v
# context[w] = refresh_context( w, context[v])
# pq.push( w)
# end
# end
# end
# end
# @end_at = Time.now
# not_found ? [] : sorted_array
# end
def path_with_cache
@path ||= path_without_cache
end
alias_method :path, :path_with_cache
def sorted_array
[].tap do |sorted_array|
previous_id = destination
previous.size.times do |t|
sorted_array.unshift(previous_id)
break if previous_id == source
previous_id = previous[previous_id]
raise "Unknown #{previous_id.inspect}" unless previous_id
end
end
end
end
end