shortest_path 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+