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 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
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop ADDED
@@ -0,0 +1,2 @@
1
+ --rails
2
+ --display-cop-names
data/.rubocop.yml ADDED
@@ -0,0 +1,12 @@
1
+ Style/BlockLength:
2
+ Exclude:
3
+ - 'dijkstra_graph.gemspec'
4
+ - 'Rakefile'
5
+ - '**/*.rake'
6
+ - 'spec/**/*.rb'
7
+
8
+ Metrics/ModuleLength:
9
+ Exclude:
10
+ - 'Rakefile'
11
+ - '**/*.rake'
12
+ - 'spec/**/*.rb'
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ cache: bundler
6
+
7
+ before_install: gem install bundler -v 1.14.6
8
+ before_script: bundle install
9
+ script: bundle exec rake spec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dijkstra_graph.gemspec
4
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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,3 @@
1
+ module DijkstraGraph
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,6 @@
1
+ require_relative './dijkstra_graph/graph'
2
+ require_relative './dijkstra_graph/version'
3
+
4
+ # A graph library supporting Dijkstra's shortest path algorithm
5
+ module DijkstraGraph
6
+ 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: []