shortest_path 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *~
6
+ coverage/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in shortest_path.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010-2012 Dryade SAS
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Shortest Path
2
+
3
+ A* ruby implementation to find shortest path and map in a graph.
4
+
5
+ ## Requirements
6
+
7
+ This code has been run and tested on Ruby 1.8
8
+
9
+ ## Installation
10
+
11
+ This package is available in RubyGems and can be installed with:
12
+
13
+ gem install shortest_path
14
+
15
+ ## More Information
16
+
17
+ More information can be found on the [project website on GitHub](http://github.com/dryade/shortest_path).
18
+ There is extensive usage documentation available [on the wiki](https://github.com/dryade/shortest_path/wiki).
19
+
20
+ ## Example Usage
21
+
22
+ ...
23
+
24
+ ## License
25
+
26
+ This project is licensed under the MIT license, a copy of which can be found in the LICENSE file.
27
+
28
+ ## Support
29
+
30
+ Users looking for support should file an issue on the GitHub issue tracking page (https://github.com/dryade/shortest_path/issues), or file a pull request (https://github.com/dryade/shortest_path/pulls) if you have a fix available.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ RSpec::Core::RakeTask.new(:rcov) do |t|
8
+ t.rcov = true
9
+ t.rcov_opts = %w{--exclude osx\/objc,gems\/,spec\/}
10
+ end
@@ -0,0 +1,118 @@
1
+ require 'pqueue'
2
+
3
+ module ShortestPath
4
+ class Finder
5
+
6
+ attr_reader :source, :destination
7
+
8
+ def initialize(source, destination)
9
+ @source, @destination = source, destination
10
+ @visited = {}
11
+ end
12
+
13
+ # Should return a map with accessible nodes and associated weight
14
+ # Example : { :a => 2, :b => 3 }
15
+ attr_accessor :ways_finder
16
+
17
+ def ways(node)
18
+ ways_finder.call node
19
+ end
20
+
21
+ def shortest_distances
22
+ @shortest_distances ||= {}
23
+ end
24
+
25
+ def previous
26
+ @previous ||= {}
27
+ end
28
+
29
+ def search_heuristic(node)
30
+ shortest_distances[node]
31
+ end
32
+
33
+ def follow_way?(node, destination, weight)
34
+ true
35
+ end
36
+
37
+ attr_accessor :timeout
38
+ attr_reader :begin_at, :end_at
39
+
40
+ def timeout?
41
+ timeout and (duration > timeout)
42
+ end
43
+
44
+ def duration
45
+ return nil unless begin_at
46
+ (end_at or Time.now) - begin_at
47
+ end
48
+
49
+ def visited?(node)
50
+ @visited[node]
51
+ end
52
+
53
+ def visit(node)
54
+ @visited[node] = true
55
+ end
56
+
57
+ def found?(node)
58
+ node == destination
59
+ end
60
+
61
+ def path_without_cache
62
+ @begin_at = Time.now
63
+
64
+ visited = {}
65
+ pq = PQueue.new do |x,y|
66
+ search_heuristic(x) < search_heuristic(y)
67
+ end
68
+
69
+ pq.push(source)
70
+ visit source
71
+ shortest_distances[source] = 0
72
+
73
+ not_found = !found?(source)
74
+
75
+ while pq.size != 0 && not_found
76
+ raise TimeoutError if timeout?
77
+
78
+ v = pq.pop
79
+ not_found = !found?(v)
80
+ visit v
81
+
82
+ weights = ways(v)
83
+ if weights
84
+ weights.keys.each do |w|
85
+ if !visited?(w) and
86
+ weights[w] and
87
+ ( shortest_distances[w].nil? || shortest_distances[w] > shortest_distances[v] + weights[w]) and
88
+ follow_way?(v, w, weights[w])
89
+ shortest_distances[w] = shortest_distances[v] + weights[w]
90
+ previous[w] = v
91
+ pq.push(w)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ @end_at = Time.now
98
+ not_found ? [] : sorted_array
99
+ end
100
+
101
+ def path_with_cache
102
+ @path ||= path_without_cache
103
+ end
104
+ alias_method :path, :path_with_cache
105
+
106
+ def sorted_array
107
+ [].tap do |sorted_array|
108
+ previous_id = destination
109
+ previous.size.times do |t|
110
+ sorted_array.unshift(previous_id)
111
+ break if previous_id == source
112
+ previous_id = previous[previous_id]
113
+ raise "Unknown #{previous_id.inspect}" unless previous_id
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,70 @@
1
+ module ShortestPath
2
+ class Map
3
+
4
+ attr_reader :source
5
+ attr_accessor :max_distance
6
+
7
+ def initialize(source)
8
+ @source = source
9
+ end
10
+
11
+ # Should return a map with accessible nodes and associated weight
12
+ # Example : { :a => 2, :b => 3 }
13
+ attr_accessor :ways_finder
14
+
15
+ def ways(node)
16
+ ways_finder.call node
17
+ end
18
+
19
+ def shortest_distances
20
+ @shortest_distances ||= {}
21
+ end
22
+
23
+ def previous
24
+ @previous ||= {}
25
+ end
26
+
27
+ def search_heuristic(node)
28
+ shortest_distances[node]
29
+ end
30
+
31
+ def map
32
+ @shortest_distances = {}
33
+ @previous = {}
34
+
35
+ visited = {}
36
+ pq = PQueue.new { |x,y| search_heuristic(x) < search_heuristic(y) }
37
+
38
+ pq.push(source)
39
+ visited[source] = true
40
+ shortest_distances[source] = 0
41
+
42
+ while pq.size != 0
43
+ v = pq.pop
44
+ visited[v] = true
45
+
46
+ weights = ways(v)
47
+ if weights
48
+ weights.keys.each do |w|
49
+ w_distance = shortest_distances[v] + weights[w]
50
+
51
+ if !visited[w] and
52
+ weights[w] and
53
+ ( shortest_distances[w].nil? || shortest_distances[w] > w_distance) and
54
+ follow_way?(v, w, weights[w])
55
+ shortest_distances[w] = w_distance
56
+ previous[w] = v
57
+ pq.push(w)
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ shortest_distances
64
+ end
65
+
66
+ def follow_way?(node, destination, weight)
67
+ max_distance.nil? or shortest_distances[node] + weight <= max_distance
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,3 @@
1
+ module ShortestPath
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,10 @@
1
+ require "shortest_path/version"
2
+
3
+ module ShortestPath
4
+ class TimeoutError < StandardError
5
+ end
6
+ end
7
+
8
+ require "shortest_path/finder"
9
+ require "shortest_path/map"
10
+
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "shortest_path/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "shortest_path"
7
+ s.version = ShortestPath::VERSION
8
+ s.authors = ["Alban Peignier", "Marc Florisson"]
9
+ s.email = ["alban@dryade.net", "marc@dryade.net"]
10
+ s.homepage = "http://github.com/dryade/shortest_path"
11
+ s.summary = %q{Ruby library to find shortest path(s) in a graph}
12
+ s.description = %q{A* ruby implementation to find shortest path and map}
13
+
14
+ s.rubyforge_project = "shortest_path"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency "pqueue"
22
+
23
+ s.add_development_dependency "rake"
24
+ s.add_development_dependency "rspec"
25
+ s.add_development_dependency "rcov"
26
+
27
+ end
@@ -0,0 +1,124 @@
1
+ require 'spec_helper'
2
+
3
+ describe ShortestPath::Finder do
4
+ let(:graph) {
5
+ { :a => { :e => 3, :b => 1, :c => 3},
6
+ :b => {:e => 1, :a => 1, :c => 3, :d => 5},
7
+ :c => {:a => 3, :b => 3, :d => 1, :s => 3},
8
+ :d => {:b => 5, :c => 1, :s => 1},
9
+ :e => {:a => 3, :b => 1},
10
+ :s => {:c => 3, :d => 1} }
11
+ }
12
+
13
+ def shortest_path(source, destination, given_graph = graph)
14
+ ShortestPath::Finder.new(source, destination).tap do |shortest_path|
15
+ shortest_path.ways_finder = Proc.new { |node| given_graph[node] }
16
+ end.path
17
+ end
18
+
19
+ it "should find shortest path in an exemple" do
20
+ shortest_path(:e, :s).should == [:e, :b, :c, :d, :s]
21
+ end
22
+
23
+ it "should return empty array when unknown start or end" do
24
+ shortest_path(:e, :unknown).should be_empty
25
+ shortest_path(:unknown, :s).should be_empty
26
+ shortest_path(:unknown, :unknown2).should be_empty
27
+ end
28
+
29
+ it "should find trivial solution" do
30
+ shortest_path(:a, :b).should == [:a, :b]
31
+ end
32
+
33
+ it "should return empty array when graph is not connex" do
34
+ not_connex = graph.clone
35
+ not_connex[:d].delete(:s)
36
+ not_connex[:c].delete(:s)
37
+
38
+ shortest_path(:e, :s, not_connex).should be_empty
39
+ end
40
+
41
+ subject {
42
+ ShortestPath::Finder.new(:e, :s).tap do |shortest_path|
43
+ shortest_path.ways_finder = Proc.new { |node| graph[node] }
44
+ end
45
+ }
46
+
47
+ describe "begin_at" do
48
+
49
+ let(:expected_time) { Time.now }
50
+
51
+ it "should be defined when path starts" do
52
+ Time.stub :now => expected_time
53
+ subject.path
54
+ subject.begin_at.should == expected_time
55
+ end
56
+
57
+ end
58
+
59
+ describe "end_at" do
60
+
61
+ let(:expected_time) { Time.now }
62
+
63
+ it "should be defined when path ends" do
64
+ Time.stub :now => expected_time
65
+ subject.path
66
+ subject.end_at.should == expected_time
67
+ end
68
+
69
+ end
70
+
71
+ describe "duration" do
72
+
73
+ it "should be nil before path is search" do
74
+ subject.duration.should be_nil
75
+ end
76
+
77
+ let(:time) { Time.now }
78
+
79
+ it "should be difference between Time.now and begin_at when path isn't ended'" do
80
+ Time.stub :now => time
81
+ subject.stub :begin_at => time - 2, :end_at => nil
82
+ subject.duration.should == 2
83
+ end
84
+
85
+ it "should be difference between end_at and begin_at when available" do
86
+ subject.stub :begin_at => time - 2, :end_at => time
87
+ subject.duration.should == 2
88
+ end
89
+
90
+ end
91
+
92
+ describe "timeout?" do
93
+
94
+ before(:each) do
95
+ subject.timeout = 2
96
+ end
97
+
98
+ it "should be false without timeout" do
99
+ subject.timeout = nil
100
+ subject.should_not be_timeout
101
+ end
102
+
103
+ it "should be false when duration is lower than timeout" do
104
+ subject.stub :duration => (subject.timeout - 1)
105
+ subject.should_not be_timeout
106
+ end
107
+
108
+ it "should be true when duration is greater than timeout" do
109
+ subject.stub :duration => (subject.timeout + 1)
110
+ subject.should be_timeout
111
+ end
112
+
113
+ end
114
+
115
+ describe "path" do
116
+
117
+ it "should raise a Timeout::Error when timeout?" do
118
+ subject.stub :timeout? => true
119
+ lambda { subject.path }.should raise_error(ShortestPath::TimeoutError)
120
+ end
121
+
122
+ end
123
+
124
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe ShortestPath::Map do
4
+
5
+ let(:graph) {
6
+ { :a => { :b => 2, :d => 1 },
7
+ :b => { :c => 1, :a => 2 },
8
+ :c => { :b => 1, :d => 3 },
9
+ :d => { :a => 1, :c => 3 } }
10
+ }
11
+
12
+ def shortest_path_map(source, max_distance = nil, given_graph = graph)
13
+ ShortestPath::Map.new(source).tap do |shortest_path_map|
14
+ shortest_path_map.max_distance = nil
15
+ shortest_path_map.ways_finder = Proc.new { |node| given_graph[node] }
16
+ end.map
17
+ end
18
+
19
+ it "should find shortest path map in an exemple" do
20
+ shortest_path_map(:a).should == { :a => 0, :b => 2, :c => 3, :d => 1 }
21
+ end
22
+
23
+ end
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'rspec'
5
+
6
+ require 'shortest_path' # and any other gems you need
7
+
8
+ Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+ # some (optional) config here
12
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shortest_path
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Alban Peignier
14
+ - Marc Florisson
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2013-04-30 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :runtime
23
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ requirement: *id001
33
+ prerelease: false
34
+ name: pqueue
35
+ - !ruby/object:Gem::Dependency
36
+ type: :development
37
+ version_requirements: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ requirement: *id002
47
+ prerelease: false
48
+ name: rake
49
+ - !ruby/object:Gem::Dependency
50
+ type: :development
51
+ version_requirements: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ requirement: *id003
61
+ prerelease: false
62
+ name: rspec
63
+ - !ruby/object:Gem::Dependency
64
+ type: :development
65
+ version_requirements: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ requirement: *id004
75
+ prerelease: false
76
+ name: rcov
77
+ description: A* ruby implementation to find shortest path and map
78
+ email:
79
+ - alban@dryade.net
80
+ - marc@dryade.net
81
+ executables: []
82
+
83
+ extensions: []
84
+
85
+ extra_rdoc_files: []
86
+
87
+ files:
88
+ - .gitignore
89
+ - Gemfile
90
+ - LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - lib/shortest_path.rb
94
+ - lib/shortest_path/finder.rb
95
+ - lib/shortest_path/map.rb
96
+ - lib/shortest_path/version.rb
97
+ - shortest_path.gemspec
98
+ - spec/shortest_path/finder_spec.rb
99
+ - spec/shortest_path/map_spec.rb
100
+ - spec/spec_helper.rb
101
+ homepage: http://github.com/dryade/shortest_path
102
+ licenses: []
103
+
104
+ post_install_message:
105
+ rdoc_options: []
106
+
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ hash: 3
115
+ segments:
116
+ - 0
117
+ version: "0"
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ hash: 3
124
+ segments:
125
+ - 0
126
+ version: "0"
127
+ requirements: []
128
+
129
+ rubyforge_project: shortest_path
130
+ rubygems_version: 1.8.24
131
+ signing_key:
132
+ specification_version: 3
133
+ summary: Ruby library to find shortest path(s) in a graph
134
+ test_files: []
135
+