dijkstra_graph 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop +2 -0
- data/.rubocop.yml +12 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +66 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/dijkstra_graph.gemspec +49 -0
- data/lib/dijkstra_graph/graph.rb +107 -0
- data/lib/dijkstra_graph/version.rb +3 -0
- data/lib/dijkstra_graph.rb +6 -0
- data/lib/util/path_util.rb +37 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 40d8dfafa22964edb55043f62194663df6ffe708
|
4
|
+
data.tar.gz: 7c76517bd99090caca0eb6bafb488c147851391f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 06cd0b89391979779f79bca07e34d8ea6e4917249d9d7d3a748f9c7843859e7b83acf6d6d248cc405e6a9683f3d3fbd5cbafb9283c4d5b8b626c483adf99cecf
|
7
|
+
data.tar.gz: 610fce885958f2aa28ae069448368fb080df6a24f74ab265d955b2d4739e4c281ea83bb5e13df4ca877c673979b7ed1bccba276c0fb08ce4517c9ac4092a2bbd
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Mark Sayson
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# dijkstra_graph
|
2
|
+
|
3
|
+
dijkstra_graph is a Ruby library implementing Dijkstra's shortest path algorithm.
|
4
|
+
|
5
|
+
It uses a Fibonnaci heap for amortized O(1) updates of vertex distances, and an adjacency list for edges for O(1) look-up of the neighbours list for the current vertex.
|
6
|
+
|
7
|
+
## DijkstraGraph::Graph API
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# Add directed edge (source, destination) to the graph with given weight
|
11
|
+
# Requires that weight is a positive number
|
12
|
+
add_edge(source, destination, weight)
|
13
|
+
|
14
|
+
# Add undirected edge (vertex_a, vertex_b) to the graph with given weight
|
15
|
+
# Requires that weight is a positive number
|
16
|
+
add_undirected_edge(vertex_a, vertex_b, weight)
|
17
|
+
|
18
|
+
# Remove directed edge (source, destination) from the graph
|
19
|
+
remove_edge(source, destination)
|
20
|
+
|
21
|
+
# Remove undirected edge (vertex_a, vertex_b) from the graph
|
22
|
+
remove_undirected_edge(vertex_a, vertex_b)
|
23
|
+
|
24
|
+
# Return true iff the graph contains directed edge (source, destination)
|
25
|
+
contains_edge?(source, destination)
|
26
|
+
|
27
|
+
# Returns the weight of directed edge (source, destination),
|
28
|
+
# or returns Float::INFINITY if no such edge exists
|
29
|
+
get_edge_weight(source, destination)
|
30
|
+
|
31
|
+
# Returns the set of vertices v_i where edge (source, v_i) is in the graph
|
32
|
+
get_adjacent_vertices(source)
|
33
|
+
|
34
|
+
# Use Dijkstra's algorithm to find the shortest distances
|
35
|
+
# from the start vertex to each of the other vertices
|
36
|
+
#
|
37
|
+
# Returns a hash of form { 'start' => 0, 'a' => 3, 'b' => 4 },
|
38
|
+
# where result[v] indicates the shortest distance from start to v
|
39
|
+
shortest_distances(start)
|
40
|
+
|
41
|
+
# Use Dijkstra's algorithm to find the shortest paths
|
42
|
+
# from the start vertex to each of the other vertices
|
43
|
+
#
|
44
|
+
# Returns a hash of form { 'c' => ['a', 'b', 'c'] }, where
|
45
|
+
# result[v] indicates the shortest path from start to v
|
46
|
+
shortest_paths(start)
|
47
|
+
|
48
|
+
# Use Dijkstra's algorithm to find the shortest path
|
49
|
+
# from the start vertex to the destination vertex
|
50
|
+
#
|
51
|
+
# Returns an array of vertices along the shortest path
|
52
|
+
# of form ['a', 'b', 'c'], or [] if no such path exists
|
53
|
+
shortest_path(start, destination)
|
54
|
+
```
|
55
|
+
|
56
|
+
## Development
|
57
|
+
|
58
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
59
|
+
|
60
|
+
## Contributing
|
61
|
+
|
62
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/msayson/dijkstra_graph.
|
63
|
+
|
64
|
+
## License
|
65
|
+
|
66
|
+
The weighted_graph library is open source and available under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'dijkstra_graph'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require 'pry'
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'dijkstra_graph/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'dijkstra_graph'
|
9
|
+
spec.version = DijkstraGraph::VERSION
|
10
|
+
spec.authors = ['Mark Sayson']
|
11
|
+
spec.email = ['masayson@gmail.com']
|
12
|
+
|
13
|
+
spec.summary = "A graph implementation supporting Dijkstra's " \
|
14
|
+
'shortest path algorithm'
|
15
|
+
spec.description = spec.summary
|
16
|
+
spec.homepage = 'https://github.com/msayson/dijkstra_graph'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
|
19
|
+
# Specify hosts this gem may be pushed to.
|
20
|
+
if spec.respond_to?(:metadata)
|
21
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org/gems/dijkstra_graph'
|
22
|
+
else
|
23
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
24
|
+
'public gem pushes.'
|
25
|
+
end
|
26
|
+
|
27
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
28
|
+
f.match(%r{^(test|spec|features)/})
|
29
|
+
end
|
30
|
+
spec.bindir = 'exe'
|
31
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ['lib']
|
33
|
+
|
34
|
+
spec.add_dependency 'PriorityQueue', '~> 0.1'
|
35
|
+
spec.add_dependency 'weighted_graph', '~> 0.1'
|
36
|
+
|
37
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
38
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
39
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
40
|
+
|
41
|
+
# Pry is a useful debugging utility, uncomment for local use.
|
42
|
+
#
|
43
|
+
# To use pry:
|
44
|
+
# - Uncomment the 'spec.add_development_dependency' line below
|
45
|
+
# - Add "require 'pry'" to the relevant Ruby source file
|
46
|
+
# - Add 'binding.pry' above the line you wish to break at
|
47
|
+
# - Run 'rake spec', and the console will break at that line
|
48
|
+
# spec.add_development_dependency 'pry', '~> 0.10'
|
49
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'priority_queue'
|
2
|
+
require 'weighted_graph'
|
3
|
+
|
4
|
+
require_relative '../util/path_util'
|
5
|
+
|
6
|
+
# Dijstra graph library
|
7
|
+
module DijkstraGraph
|
8
|
+
# A graph supporting Dijkstra's shortest path algorithm
|
9
|
+
#
|
10
|
+
# Edges are stored in an adjacency list for O(1) access
|
11
|
+
# to the neighbours list of a given vertex.
|
12
|
+
#
|
13
|
+
# Vertices 'to-visit' are stored in a priority queue that
|
14
|
+
# uses a Fibonacci heap to give O(1) insert, amortized O(1)
|
15
|
+
# decrease_priority, and amortized O(log n) delete_min.
|
16
|
+
# Priority represents path distance from the start vertex.
|
17
|
+
#
|
18
|
+
# The shortest distances found so far to each vertex are
|
19
|
+
# stored in a simple hash which gives O(1) read/write.
|
20
|
+
class Graph < WeightedGraph::PositiveWeightedGraph
|
21
|
+
# Initialize graph edges
|
22
|
+
def initialize
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
# Use Dijkstra's algorithm to find the shortest distances
|
27
|
+
# from the start vertex to each of the other vertices
|
28
|
+
#
|
29
|
+
# Returns a hash of form { 'start' => 0, 'a' => 3, 'b' => 4 },
|
30
|
+
# where result[v] indicates the shortest distance from start to v
|
31
|
+
def shortest_distances(start)
|
32
|
+
distances = Hash.new { Float::INFINITY } # Initial distances = Inf
|
33
|
+
queue = initialize_queue(start) # Begin at start node
|
34
|
+
until queue.empty?
|
35
|
+
v, distances[v] = queue.delete_min # Visit next closest vertex
|
36
|
+
update_distances_to_neighbours(v, distances, queue) # Update neighbours
|
37
|
+
end
|
38
|
+
distances
|
39
|
+
end
|
40
|
+
|
41
|
+
# Use Dijkstra's algorithm to find the shortest paths
|
42
|
+
# from the start vertex to each of the other vertices
|
43
|
+
#
|
44
|
+
# Returns a hash of form { 'c' => ['a', 'b', 'c'] }, where
|
45
|
+
# result[v] indicates the shortest path from start to v
|
46
|
+
def shortest_paths(start)
|
47
|
+
predecessors = {} # Initialize vertex predecessors
|
48
|
+
distances = Hash.new { Float::INFINITY } # Initialize distances to Inf
|
49
|
+
queue = initialize_queue(start) # Initialize queue with start
|
50
|
+
until queue.empty?
|
51
|
+
# Visit next closest vertex and update neighbours
|
52
|
+
v, distances[v] = queue.delete_min
|
53
|
+
update_paths_to_neighbours(v, predecessors, distances, queue)
|
54
|
+
end
|
55
|
+
PathUtil.path_arrays(predecessors, start)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Use Dijkstra's algorithm to find the shortest path
|
59
|
+
# from the start vertex to the destination vertex
|
60
|
+
#
|
61
|
+
# Returns an array of vertices along the shortest path
|
62
|
+
# of form ['a', 'b', 'c'], or [] if no such path exists
|
63
|
+
def shortest_path(start, dest)
|
64
|
+
predecessors = {} # Initialize vertex predecessors
|
65
|
+
distances = Hash.new { Float::INFINITY } # Initialize distances to Inf
|
66
|
+
queue = initialize_queue(start) # Initialize queue with start
|
67
|
+
until queue.empty?
|
68
|
+
v, distances[v] = queue.delete_min # Visit next closest node
|
69
|
+
return PathUtil.path_array(predecessors, start, dest) if v == dest
|
70
|
+
update_paths_to_neighbours(v, predecessors, distances, queue)
|
71
|
+
end
|
72
|
+
[] # No path found from start to dest
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# Initialize priority queue with start vertex at distance = 0
|
78
|
+
def initialize_queue(start_vertex)
|
79
|
+
queue = PriorityQueue.new
|
80
|
+
queue[start_vertex] = 0
|
81
|
+
queue
|
82
|
+
end
|
83
|
+
|
84
|
+
# Update distances to neighbours of v and queue changed neighbours
|
85
|
+
def update_distances_to_neighbours(v, distances, queue)
|
86
|
+
distance_v = distances[v]
|
87
|
+
get_adjacent_vertices(v).each do |w|
|
88
|
+
distance_through_v = distance_v + get_edge_weight(v, w)
|
89
|
+
if distance_through_v < distances[w]
|
90
|
+
queue[w] = distances[w] = distance_through_v
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Update paths to neighbours of v and queue changed neighbours
|
96
|
+
def update_paths_to_neighbours(v, predecessors, distances, queue)
|
97
|
+
distance_v = distances[v]
|
98
|
+
get_adjacent_vertices(v).each do |w|
|
99
|
+
distance_through_v = distance_v + get_edge_weight(v, w)
|
100
|
+
if distance_through_v < distances[w]
|
101
|
+
queue[w] = distances[w] = distance_through_v
|
102
|
+
predecessors[w] = v
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Utility functions for path manipulation
|
2
|
+
module PathUtil
|
3
|
+
# Returns hash mapping each vertex to the shortest path
|
4
|
+
# from the start to that vertex
|
5
|
+
def self.path_arrays(predecessors, start)
|
6
|
+
paths = Hash.new { [] }
|
7
|
+
start_has_outgoing_edges = false
|
8
|
+
predecessors.each do |v, pred|
|
9
|
+
start_has_outgoing_edges = true if pred == start
|
10
|
+
paths[v] = path_to_vertex(paths, predecessors, start, v)
|
11
|
+
end
|
12
|
+
start_has_outgoing_edges ? paths : {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns array of vertices on shortest path from start to destination
|
16
|
+
def self.path_array(predecessors, start, destination)
|
17
|
+
path = []
|
18
|
+
curr_vertex = destination
|
19
|
+
loop do
|
20
|
+
path.push(curr_vertex)
|
21
|
+
return path.reverse if curr_vertex == start
|
22
|
+
curr_vertex = predecessors[curr_vertex]
|
23
|
+
return [] if curr_vertex.nil?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns path to vertex, re-using existing paths
|
28
|
+
# if already have the path to v's predecessor
|
29
|
+
def self.path_to_vertex(paths, predecessors, start, v)
|
30
|
+
pred = predecessors[v]
|
31
|
+
if paths.include?(pred)
|
32
|
+
paths[pred] + [v]
|
33
|
+
else
|
34
|
+
path_array(predecessors, start, v)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dijkstra_graph
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mark Sayson
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: PriorityQueue
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: weighted_graph
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.14'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.14'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
description: A graph implementation supporting Dijkstra's shortest path algorithm
|
84
|
+
email:
|
85
|
+
- masayson@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".rubocop"
|
93
|
+
- ".rubocop.yml"
|
94
|
+
- ".travis.yml"
|
95
|
+
- Gemfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- bin/console
|
100
|
+
- bin/setup
|
101
|
+
- dijkstra_graph.gemspec
|
102
|
+
- lib/dijkstra_graph.rb
|
103
|
+
- lib/dijkstra_graph/graph.rb
|
104
|
+
- lib/dijkstra_graph/version.rb
|
105
|
+
- lib/util/path_util.rb
|
106
|
+
homepage: https://github.com/msayson/dijkstra_graph
|
107
|
+
licenses:
|
108
|
+
- MIT
|
109
|
+
metadata:
|
110
|
+
allowed_push_host: https://rubygems.org/gems/dijkstra_graph
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubyforge_project:
|
127
|
+
rubygems_version: 2.6.11
|
128
|
+
signing_key:
|
129
|
+
specification_version: 4
|
130
|
+
summary: A graph implementation supporting Dijkstra's shortest path algorithm
|
131
|
+
test_files: []
|