dijkstra_graph 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []