shortest_path 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/dryade/shortest_path.png)](http://travis-ci.org/dryade/shortest_path?branch=master) [![Dependency Status](https://gemnasium.com/dryade/shortest_path.png)](https://gemnasium.com/dryade/shortest_path) [![Code Climate](https://codeclimate.com/github/dryade/shortest_path.png)](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
|