shortest_path 0.0.2 → 0.0.3
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/.rvmrc +1 -0
- data/{LICENSE.txt → MIT-LICENSE} +0 -0
- data/README.md +55 -4
- data/lib/shortest_path/finder.rb +26 -11
- data/lib/shortest_path/version.rb +1 -1
- data/spec/shortest_path/finder_spec.rb +33 -9
- metadata +6 -5
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm ruby-1.9.3-head
|
data/{LICENSE.txt → MIT-LICENSE}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -1,18 +1,37 @@
|
|
1
1
|
# Shortest Path [](http://travis-ci.org/dryade/shortest_path?branch=master) [](https://gemnasium.com/dryade/shortest_path) [](https://codeclimate.com/github/dryade/shortest_path)
|
2
2
|
|
3
|
-
A* ruby implementation to find shortest path and map in a graph
|
3
|
+
A* ruby implementation to find shortest path and map in a graph with :
|
4
|
+
- a timeout to stop research when duration > timeout
|
5
|
+
- a context in a hash for each point in the graph
|
6
|
+
- the possibility to override default methods
|
7
|
+
|
4
8
|
|
5
9
|
Requirements
|
6
10
|
------------
|
7
11
|
|
8
|
-
This code has been run and tested on Ruby 1.
|
12
|
+
This code has been run and tested on Ruby 1.9
|
13
|
+
|
14
|
+
External Deps
|
15
|
+
-------------
|
16
|
+
On Debian/Ubuntu/Kubuntu OS :
|
17
|
+
```sh
|
18
|
+
sudo apt-get install git
|
19
|
+
```
|
9
20
|
|
10
21
|
Installation
|
11
22
|
------------
|
12
23
|
|
13
24
|
This package is available in RubyGems and can be installed with:
|
14
|
-
|
15
|
-
|
25
|
+
```sh
|
26
|
+
gem install shortest_path
|
27
|
+
```
|
28
|
+
|
29
|
+
Test
|
30
|
+
----
|
31
|
+
|
32
|
+
```sh
|
33
|
+
bundle exec rake spec
|
34
|
+
```
|
16
35
|
|
17
36
|
More Information
|
18
37
|
----------------
|
@@ -23,8 +42,40 @@ There is extensive usage documentation available [on the wiki](https://github.co
|
|
23
42
|
Example Usage
|
24
43
|
-------------
|
25
44
|
|
45
|
+
Create a basic shortest path finder :
|
46
|
+
```ruby
|
47
|
+
# Create a graph
|
48
|
+
graph = { :a => { :e => 3, :b => 1, :c => 3},
|
49
|
+
:b => {:e => 1, :a => 1, :c => 3, :d => 5},
|
50
|
+
:c => {:a => 3, :b => 3, :d => 1, :s => 3},
|
51
|
+
:d => {:b => 5, :c => 1, :s => 1},
|
52
|
+
:e => {:a => 3, :b => 1},
|
53
|
+
:s => {:c => 3, :d => 1} }
|
54
|
+
}
|
55
|
+
|
56
|
+
# Create a finder
|
57
|
+
finder = ShortestPath::Finder.new(:a, :e).tap do |shortest_path|
|
58
|
+
shortest_path.ways_finder = Proc.new { |node| graph[node] }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Change the timeout in seconds
|
62
|
+
finder.timeout = 2
|
63
|
+
|
64
|
+
# Call graph result
|
65
|
+
finder.path
|
66
|
+
|
67
|
+
```
|
68
|
+
|
69
|
+
Overwrite shortest path finder :
|
70
|
+
```ruby
|
71
|
+
|
72
|
+
# TODO : Class that overwrites shortest path finder
|
26
73
|
...
|
27
74
|
|
75
|
+
|
76
|
+
```
|
77
|
+
|
78
|
+
|
28
79
|
License
|
29
80
|
-------
|
30
81
|
|
data/lib/shortest_path/finder.rb
CHANGED
@@ -14,7 +14,11 @@ module ShortestPath
|
|
14
14
|
# Example : { :a => 2, :b => 3 }
|
15
15
|
attr_accessor :ways_finder
|
16
16
|
|
17
|
-
def
|
17
|
+
def refresh_context( node, context)
|
18
|
+
{}
|
19
|
+
end
|
20
|
+
|
21
|
+
def ways(node, context={})
|
18
22
|
ways_finder.call node
|
19
23
|
end
|
20
24
|
|
@@ -26,17 +30,26 @@ module ShortestPath
|
|
26
30
|
@previous ||= {}
|
27
31
|
end
|
28
32
|
|
33
|
+
# @context is a hash
|
34
|
+
# each node is related to an hash defining the context
|
35
|
+
# context is specific to the path solution between departure dans target node
|
36
|
+
#
|
37
|
+
def context
|
38
|
+
@context ||= {}
|
39
|
+
end
|
40
|
+
|
29
41
|
def search_heuristic(node)
|
30
42
|
shortest_distances[node]
|
31
43
|
end
|
32
44
|
|
33
|
-
def follow_way?(node, destination, weight)
|
45
|
+
def follow_way?(node, destination, weight, context={})
|
34
46
|
true
|
35
47
|
end
|
36
48
|
|
49
|
+
# timeout is in seconds
|
37
50
|
attr_accessor :timeout
|
38
51
|
attr_reader :begin_at, :end_at
|
39
|
-
|
52
|
+
|
40
53
|
def timeout?
|
41
54
|
timeout and (duration > timeout)
|
42
55
|
end
|
@@ -47,7 +60,7 @@ module ShortestPath
|
|
47
60
|
end
|
48
61
|
|
49
62
|
def visited?(node)
|
50
|
-
@visited[node]
|
63
|
+
@visited[node]
|
51
64
|
end
|
52
65
|
|
53
66
|
def visit(node)
|
@@ -60,15 +73,16 @@ module ShortestPath
|
|
60
73
|
|
61
74
|
def path_without_cache
|
62
75
|
@begin_at = Time.now
|
63
|
-
|
76
|
+
|
64
77
|
visited = {}
|
65
|
-
pq = PQueue.new do |x,y|
|
78
|
+
pq = PQueue.new do |x,y|
|
66
79
|
search_heuristic(x) < search_heuristic(y)
|
67
80
|
end
|
68
81
|
|
69
|
-
pq.push(source)
|
82
|
+
pq.push( source)
|
70
83
|
visit source
|
71
84
|
shortest_distances[source] = 0
|
85
|
+
context[source] = {}
|
72
86
|
|
73
87
|
not_found = !found?(source)
|
74
88
|
|
@@ -79,16 +93,17 @@ module ShortestPath
|
|
79
93
|
not_found = !found?(v)
|
80
94
|
visit v
|
81
95
|
|
82
|
-
weights = ways(v)
|
96
|
+
weights = ways(v, context[v])
|
83
97
|
if weights
|
84
98
|
weights.keys.each do |w|
|
85
99
|
if !visited?(w) and
|
86
100
|
weights[w] and
|
87
|
-
( shortest_distances[w].nil? || shortest_distances[w] > shortest_distances[v] + weights[w]) and
|
88
|
-
follow_way?(v, w, weights[w])
|
101
|
+
( shortest_distances[w].nil? || shortest_distances[w] > shortest_distances[v] + weights[w]) and
|
102
|
+
follow_way?(v, w, weights[w], context[v])
|
89
103
|
shortest_distances[w] = shortest_distances[v] + weights[w]
|
90
104
|
previous[w] = v
|
91
|
-
|
105
|
+
context[w] = refresh_context( w, context[v])
|
106
|
+
pq.push( w)
|
92
107
|
end
|
93
108
|
end
|
94
109
|
end
|
@@ -1,5 +1,17 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
class TestContextualFinder < ShortestPath::Finder
|
4
|
+
def refresh_context( node, context)
|
5
|
+
count = context[:edges_count] ? context[:edges_count] : 0
|
6
|
+
return { :edges_count => (count + 1)}
|
7
|
+
end
|
8
|
+
|
9
|
+
def follow_way?(node, destination, weight, context={})
|
10
|
+
return context[:edges_count].nil? || context[:edges_count] < 3
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
3
15
|
describe ShortestPath::Finder do
|
4
16
|
let(:graph) {
|
5
17
|
{ :a => { :e => 3, :b => 1, :c => 3},
|
@@ -10,6 +22,18 @@ describe ShortestPath::Finder do
|
|
10
22
|
:s => {:c => 3, :d => 1} }
|
11
23
|
}
|
12
24
|
|
25
|
+
def contextual_shortest_path(source, destination, given_graph = graph)
|
26
|
+
TestContextualFinder.new(source, destination).tap do |shortest_path|
|
27
|
+
shortest_path.ways_finder = Proc.new { |node| given_graph[node] }
|
28
|
+
end.path
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when using an edge_count filter in context " do
|
32
|
+
it "should find shortest path in an exemple" do
|
33
|
+
contextual_shortest_path(:e, :s).should == [:e, :b, :c, :s]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
13
37
|
def shortest_path(source, destination, given_graph = graph)
|
14
38
|
ShortestPath::Finder.new(source, destination).tap do |shortest_path|
|
15
39
|
shortest_path.ways_finder = Proc.new { |node| given_graph[node] }
|
@@ -38,16 +62,16 @@ describe ShortestPath::Finder do
|
|
38
62
|
shortest_path(:e, :s, not_connex).should be_empty
|
39
63
|
end
|
40
64
|
|
41
|
-
subject {
|
65
|
+
subject {
|
42
66
|
ShortestPath::Finder.new(:e, :s).tap do |shortest_path|
|
43
67
|
shortest_path.ways_finder = Proc.new { |node| graph[node] }
|
44
68
|
end
|
45
69
|
}
|
46
70
|
|
47
71
|
describe "begin_at" do
|
48
|
-
|
72
|
+
|
49
73
|
let(:expected_time) { Time.now }
|
50
|
-
|
74
|
+
|
51
75
|
it "should be defined when path starts" do
|
52
76
|
Time.stub :now => expected_time
|
53
77
|
subject.path
|
@@ -57,9 +81,9 @@ describe ShortestPath::Finder do
|
|
57
81
|
end
|
58
82
|
|
59
83
|
describe "end_at" do
|
60
|
-
|
84
|
+
|
61
85
|
let(:expected_time) { Time.now }
|
62
|
-
|
86
|
+
|
63
87
|
it "should be defined when path ends" do
|
64
88
|
Time.stub :now => expected_time
|
65
89
|
subject.path
|
@@ -69,7 +93,7 @@ describe ShortestPath::Finder do
|
|
69
93
|
end
|
70
94
|
|
71
95
|
describe "duration" do
|
72
|
-
|
96
|
+
|
73
97
|
it "should be nil before path is search" do
|
74
98
|
subject.duration.should be_nil
|
75
99
|
end
|
@@ -99,12 +123,12 @@ describe ShortestPath::Finder do
|
|
99
123
|
subject.timeout = nil
|
100
124
|
subject.should_not be_timeout
|
101
125
|
end
|
102
|
-
|
126
|
+
|
103
127
|
it "should be false when duration is lower than timeout" do
|
104
128
|
subject.stub :duration => (subject.timeout - 1)
|
105
129
|
subject.should_not be_timeout
|
106
130
|
end
|
107
|
-
|
131
|
+
|
108
132
|
it "should be true when duration is greater than timeout" do
|
109
133
|
subject.stub :duration => (subject.timeout + 1)
|
110
134
|
subject.should be_timeout
|
@@ -113,7 +137,7 @@ describe ShortestPath::Finder do
|
|
113
137
|
end
|
114
138
|
|
115
139
|
describe "path" do
|
116
|
-
|
140
|
+
|
117
141
|
it "should raise a Timeout::Error when timeout?" do
|
118
142
|
subject.stub :timeout? => true
|
119
143
|
lambda { subject.path }.should raise_error(ShortestPath::TimeoutError)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shortest_path
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Alban Peignier
|
@@ -17,7 +17,7 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2013-
|
20
|
+
date: 2013-08-12 00:00:00 Z
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
23
23
|
type: :development
|
@@ -116,10 +116,11 @@ extra_rdoc_files: []
|
|
116
116
|
|
117
117
|
files:
|
118
118
|
- .gitignore
|
119
|
+
- .rvmrc
|
119
120
|
- .travis.yml
|
120
121
|
- Gemfile
|
121
122
|
- Guardfile
|
122
|
-
- LICENSE
|
123
|
+
- MIT-LICENSE
|
123
124
|
- README.md
|
124
125
|
- Rakefile
|
125
126
|
- lib/shortest_path.rb
|