dijkstra_fast 1.4.2 → 1.5.2
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 +4 -4
- data/.yardopts +7 -0
- data/README.md +102 -8
- data/Rakefile +6 -9
- data/dijkstra_fast.gemspec +14 -8
- data/ext/dijkstra_fast/dijkstra_fast.c +2 -2
- data/ext/dijkstra_fast/errors.c +12 -0
- data/ext/dijkstra_fast/errors.h +7 -0
- data/ext/dijkstra_fast/expand_capacity.h +19 -0
- data/ext/dijkstra_fast/native.c +80 -0
- data/ext/dijkstra_fast/native.h +57 -0
- data/ext/dijkstra_fast/native_shortest_path.c +90 -0
- data/ext/dijkstra_fast/priority_queue.c +109 -0
- data/ext/dijkstra_fast/priority_queue.h +42 -0
- data/lib/dijkstra_fast/graph.rb +9 -19
- data/lib/dijkstra_fast/native.rb +68 -0
- data/lib/dijkstra_fast/shortest_path.rb +96 -0
- data/lib/dijkstra_fast/version.rb +1 -1
- data/lib/dijkstra_fast.rb +79 -9
- metadata +44 -9
- data/ext/dijkstra_fast/dijkstra_graph.c +0 -234
- data/ext/dijkstra_fast/dijkstra_graph.h +0 -83
- data/ext/dijkstra_fast/prioritized_item_list.c +0 -128
- data/ext/dijkstra_fast/prioritized_item_list.h +0 -39
- data/lib/dijkstra_fast/best_path.rb +0 -17
data/lib/dijkstra_fast/graph.rb
CHANGED
@@ -24,8 +24,10 @@ module DijkstraFast
|
|
24
24
|
##
|
25
25
|
class Graph
|
26
26
|
|
27
|
+
include DijkstraFast::ShortestPath
|
28
|
+
|
27
29
|
def initialize
|
28
|
-
@
|
30
|
+
@edges = {}
|
29
31
|
end
|
30
32
|
|
31
33
|
# Adds a weighted edge to the graph. This represents a possible path from the
|
@@ -36,27 +38,15 @@ module DijkstraFast
|
|
36
38
|
# If not provided, a default distance of `1` is used.
|
37
39
|
# @return [nil]
|
38
40
|
def add(source, dest, distance: 1)
|
39
|
-
|
40
|
-
end
|
41
|
+
return if source == dest
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
# @param source [Object] Any Ruby object that represents the source item
|
45
|
-
# @param dest [Object] Any Ruby object that represents the destination item
|
46
|
-
# @return [BestPath]
|
47
|
-
def shortest_path(source, dest)
|
48
|
-
best_path = BestPath.new
|
49
|
-
best_path.distance = _shortest_path(node(source), node(dest), best_path.path)
|
50
|
-
if best_path.path.empty? || best_path.distance.nil? || best_path.distance < 0
|
51
|
-
raise NoPathExistsError
|
52
|
-
end
|
53
|
-
best_path
|
43
|
+
@edges[source] ||= []
|
44
|
+
@edges[source] << [dest, distance]
|
54
45
|
end
|
55
46
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
@nodes[obj] ||= @nodes.size # Auto-increment id
|
47
|
+
def connections(source, &block)
|
48
|
+
@edges[source]&.each(&block)
|
49
|
+
nil
|
60
50
|
end
|
61
51
|
|
62
52
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module DijkstraFast
|
2
|
+
##
|
3
|
+
# Do not use this class. For internal purposes only.
|
4
|
+
##
|
5
|
+
class Native
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def initialize(delegate)
|
10
|
+
@delegate = delegate
|
11
|
+
@ids = {}
|
12
|
+
@nodes = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def connections(source_id)
|
16
|
+
@delegate.connections(node(source_id)) do |dest, distance = 1|
|
17
|
+
yield node_id(dest), distance
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def shortest_path(source, dest, progress: false)
|
22
|
+
if !progress
|
23
|
+
progress = nil
|
24
|
+
elsif !progress.is_a?(Proc)
|
25
|
+
progress_bar = create_progress_bar(progress)
|
26
|
+
progress = lambda do |completed, total|
|
27
|
+
progress_bar.total = total
|
28
|
+
progress_bar.progress = completed
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
distance, path = _shortest_path(node_id(source), node_id(dest), progress)
|
33
|
+
[distance, @ids.invert.values_at(*path)]
|
34
|
+
ensure
|
35
|
+
progress_bar&.clear
|
36
|
+
end
|
37
|
+
|
38
|
+
def node_id(node)
|
39
|
+
id = @ids[node]
|
40
|
+
if id.nil?
|
41
|
+
id = @ids[node] = @ids.size + 1 # Auto-incrementing id (starting at 1 to leave 0 = nil)
|
42
|
+
@nodes[id] = node
|
43
|
+
end
|
44
|
+
id
|
45
|
+
end
|
46
|
+
|
47
|
+
def node(id)
|
48
|
+
@nodes.fetch(id)
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_progress_bar(config)
|
52
|
+
require 'progressbar' unless defined?(ProgressBar)
|
53
|
+
return ProgressBar.create(config) if config.is_a?(Hash)
|
54
|
+
|
55
|
+
format = config.is_a?(String) ? config : "%a %b\u{15E7}%i %p%% %t"
|
56
|
+
ProgressBar.create(
|
57
|
+
format: format,
|
58
|
+
progress_mark: ' ',
|
59
|
+
remainder_mark: "\u{FF65}",
|
60
|
+
total: 100,
|
61
|
+
autofinish: false,
|
62
|
+
)
|
63
|
+
rescue LoadError
|
64
|
+
raise LoadError, 'The default implementation of progress reporting requires the progressbar gem'
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module DijkstraFast
|
2
|
+
##
|
3
|
+
# Interface to allow arbitrary classes to calculate the shortest path between
|
4
|
+
# two "items" using a native implementation of Dijkstra's algorithm.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
#
|
8
|
+
# class GraphOne
|
9
|
+
# include DijkstraFast::ShortestPath
|
10
|
+
#
|
11
|
+
# def connections(source)
|
12
|
+
# source.find_next do |dest, distance|
|
13
|
+
# yield dest, distance
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# distance, path = GraphOne.new.shortest_path(source, dest)
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
#
|
22
|
+
# class GraphTwo
|
23
|
+
# include DijkstraFast::ShortestPath
|
24
|
+
#
|
25
|
+
# def initialize
|
26
|
+
# # All edges have distance 1
|
27
|
+
# @edges = { 1 => [2, 3], 2 => [3] }
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# def connections(source, &block)
|
31
|
+
# @edges.fetch(source, []).each(&block)
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# distance = GraphTwo.new.shortest_distance(source, dest)
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
#
|
39
|
+
# class GraphThree
|
40
|
+
# include DijkstraFast::ShortestPath
|
41
|
+
#
|
42
|
+
# def connections(source)
|
43
|
+
# case source
|
44
|
+
# when 'A'
|
45
|
+
# yield 'B', 3
|
46
|
+
# yield 'C' # Default distance 1
|
47
|
+
# when 'B'
|
48
|
+
# yield 'C', 10
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# distance, path = GraphThree.new.shortest_path(source, dest)
|
54
|
+
#
|
55
|
+
# @see README
|
56
|
+
# @see https://en.wikipedia.org/wiki/Dijkstra's_algorithm
|
57
|
+
##
|
58
|
+
module ShortestPath
|
59
|
+
|
60
|
+
# Returns the edges originating at source
|
61
|
+
# @param source [Object] Any Ruby object that represents the source item
|
62
|
+
# @yield [Object, int] A connection to destination object with given distance
|
63
|
+
# @return [nil]
|
64
|
+
def connections(source)
|
65
|
+
# Does nothing by default but should be implemented by class
|
66
|
+
end
|
67
|
+
|
68
|
+
# Finds the shortest path between items, returning both the path as well as
|
69
|
+
# the total distance travelled.
|
70
|
+
# @param source [Object] Any Ruby object that represents the source item
|
71
|
+
# @param dest [Object] Any Ruby object that represents the destination item
|
72
|
+
# @param progress [Boolean|String|Proc] If falsey (default), no progress will
|
73
|
+
# be displayed. If a Proc is given, it will be yielded progress
|
74
|
+
# (completed / total) as progress is made. If otherwise truthy the
|
75
|
+
# ruby-progressbar gem will be required and displayed; if passed a
|
76
|
+
# String it will be used as the progress bar's format.
|
77
|
+
# @return [Array<int, Array>]
|
78
|
+
def shortest_path(source, dest, progress: false)
|
79
|
+
Native.new(self).send(:shortest_path, source, dest, progress: progress)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Finds the shortest distance between items
|
83
|
+
# @param source [Object] Any Ruby object that represents the source item
|
84
|
+
# @param dest [Object] Any Ruby object that represents the destination item
|
85
|
+
# @param progress [Boolean|String|Proc] If falsey (default), no progress will
|
86
|
+
# be displayed. If a Proc is given, it will be yielded progress
|
87
|
+
# (completed / total) as progress is made. If otherwise truthy the
|
88
|
+
# ruby-progressbar gem will be required and displayed; if passed a
|
89
|
+
# String it will be used as the progress bar's format.
|
90
|
+
# @return [int]
|
91
|
+
def shortest_distance(source, dest, progress: false)
|
92
|
+
shortest_path(source, dest, progress: progress).first
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
data/lib/dijkstra_fast.rb
CHANGED
@@ -1,22 +1,92 @@
|
|
1
1
|
##
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# When graph nodes "know" which nodes they are connected to, one of the two
|
3
|
+
# class methods on this module can be be used directly to calculate the shortest
|
4
|
+
# distance between nodes. Node objects must implement a method (by default
|
5
|
+
# <code>connections</code>) which yields all connected nodes and (optionally) the
|
6
|
+
# distance to that connected node. If the yield only supplies the connected
|
7
|
+
# node, a defualt distance of <code>1</code> is used.
|
4
8
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# footprint when operating on very large graphs where the average number of edges
|
8
|
-
# between nodes is relatively small (e.g. < 1/10 the number of nodes). See
|
9
|
+
# It is important to ensure that <code>hash</code> and <code>eql?</code> are properly implemented so
|
10
|
+
# that two objects that are logically the same are treatest thusly.
|
9
11
|
#
|
10
|
-
# @
|
11
|
-
#
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# class SomethingWithConnections
|
15
|
+
# def find_next
|
16
|
+
# yield SomethingWithConnections.new('C'), 2
|
17
|
+
# yield SomethingWithConnections.new('D'), 4
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def hash
|
21
|
+
# # implement me
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def eql?(other)
|
25
|
+
# # implement me
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# a = SomethingWithConnections.new('A')
|
30
|
+
# b = SomethingWithConnections.new('B')
|
31
|
+
# distance, path = DijkstraFast.shortest_path(a, b, connections: :find_next)
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
#
|
35
|
+
# class SomethingElseWithConnections
|
36
|
+
# def connections
|
37
|
+
# yield # implement me
|
38
|
+
# yield # implement me
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# a = SomethingElseWithConnections.new
|
43
|
+
# b = SomethingElseWithConnections.new
|
44
|
+
# distance = DijkstraFast.shortest_distance(a, b)
|
12
45
|
##
|
13
46
|
module DijkstraFast
|
14
47
|
|
15
|
-
autoload :BestPath, 'dijkstra_fast/best_path'
|
16
48
|
autoload :Graph, 'dijkstra_fast/graph'
|
49
|
+
autoload :Native, 'dijkstra_fast/native'
|
17
50
|
autoload :NoPathExistsError, 'dijkstra_fast/no_path_exists_error'
|
51
|
+
autoload :PriorityQueue, 'dijkstra_fast/priority_queue'
|
52
|
+
autoload :ShortestPath, 'dijkstra_fast/shortest_path'
|
18
53
|
autoload :VERSION, 'dijkstra_fast/version'
|
19
54
|
|
55
|
+
# Finds the shortest path between items, returning both the path as well as
|
56
|
+
# the total distance travelled.
|
57
|
+
# @param source [Object] Any Ruby object that represents the source item
|
58
|
+
# @param dest [Object] Any Ruby object that represents the destination item
|
59
|
+
# @param progress [Boolean|String|Proc] If falsey (default), no progress will
|
60
|
+
# be displayed. If a Proc is given, it will be yielded progress
|
61
|
+
# (completed / total) as progress is made. If otherwise truthy the
|
62
|
+
# ruby-progressbar gem will be required and displayed; if passed a
|
63
|
+
# String it will be used as the progress bar's format.
|
64
|
+
# @param connections [Symbol|String] The method to call on each item to
|
65
|
+
# obtain adjacent items in the graph. Defaults to 'connections'
|
66
|
+
# @return [Array<int, Array>]
|
67
|
+
def self.shortest_path(source, dest, connections: 'connections', progress: false)
|
68
|
+
clazz = Class.new { include DijkstraFast::ShortestPath }
|
69
|
+
clazz.define_method(:connections) do |source, &block|
|
70
|
+
source.send(connections, &block)
|
71
|
+
end
|
72
|
+
clazz.new.shortest_path(source, dest, progress: progress)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Finds the shortest distance between items
|
76
|
+
# @param source [Object] Any Ruby object that represents the source item
|
77
|
+
# @param dest [Object] Any Ruby object that represents the destination item
|
78
|
+
# @param progress [Boolean|String|Proc] If falsey (default), no progress will
|
79
|
+
# be displayed. If a Proc is given, it will be yielded progress
|
80
|
+
# (completed / total) as progress is made. If otherwise truthy the
|
81
|
+
# ruby-progressbar gem will be required and displayed; if passed a
|
82
|
+
# String it will be used as the progress bar's format.
|
83
|
+
# @param connections [Symbol|String] The method to call on each item to
|
84
|
+
# obtain adjacent items in the graph.
|
85
|
+
# @return [int]
|
86
|
+
def self.shortest_distance(source, dest, connections: 'connections', progress: false)
|
87
|
+
shortest_path(source, dest, connections: connections, progress: progress).first
|
88
|
+
end
|
89
|
+
|
20
90
|
end
|
21
91
|
|
22
92
|
require 'dijkstra_fast/dijkstra_fast'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dijkstra_fast
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David McCullars
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: github-markup
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +66,20 @@ dependencies:
|
|
52
66
|
- - ">="
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: redcarpet
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: rspec
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -157,6 +185,7 @@ files:
|
|
157
185
|
- ".rubocop.yml"
|
158
186
|
- ".ruby-version"
|
159
187
|
- ".travis.yml"
|
188
|
+
- ".yardopts"
|
160
189
|
- CODE_OF_CONDUCT.md
|
161
190
|
- Gemfile
|
162
191
|
- LICENSE
|
@@ -164,20 +193,26 @@ files:
|
|
164
193
|
- Rakefile
|
165
194
|
- dijkstra_fast.gemspec
|
166
195
|
- ext/dijkstra_fast/dijkstra_fast.c
|
167
|
-
- ext/dijkstra_fast/
|
168
|
-
- ext/dijkstra_fast/
|
196
|
+
- ext/dijkstra_fast/errors.c
|
197
|
+
- ext/dijkstra_fast/errors.h
|
198
|
+
- ext/dijkstra_fast/expand_capacity.h
|
169
199
|
- ext/dijkstra_fast/extconf.rb
|
170
|
-
- ext/dijkstra_fast/
|
171
|
-
- ext/dijkstra_fast/
|
200
|
+
- ext/dijkstra_fast/native.c
|
201
|
+
- ext/dijkstra_fast/native.h
|
202
|
+
- ext/dijkstra_fast/native_shortest_path.c
|
203
|
+
- ext/dijkstra_fast/priority_queue.c
|
204
|
+
- ext/dijkstra_fast/priority_queue.h
|
172
205
|
- lib/dijkstra_fast.rb
|
173
|
-
- lib/dijkstra_fast/best_path.rb
|
174
206
|
- lib/dijkstra_fast/graph.rb
|
207
|
+
- lib/dijkstra_fast/native.rb
|
175
208
|
- lib/dijkstra_fast/no_path_exists_error.rb
|
209
|
+
- lib/dijkstra_fast/shortest_path.rb
|
176
210
|
- lib/dijkstra_fast/version.rb
|
177
211
|
homepage: https://github.com/david-mccullars/dijkstra_fast
|
178
212
|
licenses:
|
179
213
|
- MIT
|
180
|
-
metadata:
|
214
|
+
metadata:
|
215
|
+
rubygems_mfa_required: 'true'
|
181
216
|
post_install_message:
|
182
217
|
rdoc_options: []
|
183
218
|
require_paths:
|
@@ -186,7 +221,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
186
221
|
requirements:
|
187
222
|
- - ">="
|
188
223
|
- !ruby/object:Gem::Version
|
189
|
-
version:
|
224
|
+
version: 3.0.0
|
190
225
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
191
226
|
requirements:
|
192
227
|
- - ">="
|
@@ -1,234 +0,0 @@
|
|
1
|
-
#include <ruby.h>
|
2
|
-
#include <dijkstra_graph.h>
|
3
|
-
#include <prioritized_item_list.h>
|
4
|
-
|
5
|
-
const size_t VERTEX_LIST_SIZE = sizeof(VertexListStruct);
|
6
|
-
const size_t EDGE_LIST_SIZE = sizeof(EdgeListStruct);
|
7
|
-
const size_t VERTEX_SIZE = sizeof(VertexStruct);
|
8
|
-
const size_t EDGE_SIZE = sizeof(EdgeStruct);
|
9
|
-
const size_t GRAPH_SIZE = sizeof(GraphStruct);
|
10
|
-
|
11
|
-
static const rb_data_type_t graph_typed_data = {
|
12
|
-
"Dijkstra/Graph",
|
13
|
-
{ 0, free_graph, },
|
14
|
-
0, 0,
|
15
|
-
RUBY_TYPED_FREE_IMMEDIATELY,
|
16
|
-
};
|
17
|
-
|
18
|
-
//////////////////////////////////////////////////////////////////////////////////////
|
19
|
-
|
20
|
-
void Init_dijkstra_graph() {
|
21
|
-
VALUE DijkstraFastModule, GraphClass;
|
22
|
-
|
23
|
-
DijkstraFastModule = rb_const_get(rb_cObject, rb_intern("DijkstraFast"));
|
24
|
-
GraphClass = rb_const_get(DijkstraFastModule, rb_intern("Graph"));
|
25
|
-
|
26
|
-
rb_define_alloc_func(GraphClass, dijkstra_graph_allocate);
|
27
|
-
rb_define_private_method(GraphClass, "_add_edge", dijkstra_graph_add_edge, 3);
|
28
|
-
rb_define_private_method(GraphClass, "_shortest_path", dijkstra_graph_shortest_path, 3);
|
29
|
-
}
|
30
|
-
|
31
|
-
VALUE dijkstra_graph_allocate(VALUE self) {
|
32
|
-
Graph g = malloc(GRAPH_SIZE);
|
33
|
-
|
34
|
-
// Grab a reference to the hash type used by a generic Ruby {}
|
35
|
-
// which accepts any key and any value. We'll need this type to create
|
36
|
-
// a st_table in which to put arbitrary VALUE keys. This hash type
|
37
|
-
// should be a static constant and thus should be safe to utilize without
|
38
|
-
// fear of garbage collection.
|
39
|
-
const struct st_hash_type *objhash = rb_hash_tbl(rb_hash_new(), "dijkstra.c", 1)->type;
|
40
|
-
|
41
|
-
g->vertex_count = 0;
|
42
|
-
g->vertices = NULL;
|
43
|
-
g->vertex_lookup = st_init_table_with_size(objhash, 0);
|
44
|
-
|
45
|
-
return TypedData_Wrap_Struct(self, &graph_typed_data, g);
|
46
|
-
}
|
47
|
-
|
48
|
-
VALUE dijkstra_graph_add_edge(VALUE self, VALUE source_label, VALUE dest_label, VALUE distance) {
|
49
|
-
Graph g;
|
50
|
-
|
51
|
-
TypedData_Get_Struct(self, GraphStruct, &graph_typed_data, g);
|
52
|
-
add_edge_with_labels(g, source_label, dest_label, NUM2INT(distance));
|
53
|
-
return Qnil;
|
54
|
-
}
|
55
|
-
|
56
|
-
VALUE dijkstra_graph_shortest_path(VALUE self, VALUE source_label, VALUE dest_label, VALUE best_path) {
|
57
|
-
Graph g;
|
58
|
-
Vertex source, dest;
|
59
|
-
|
60
|
-
TypedData_Get_Struct(self, GraphStruct, &graph_typed_data, g);
|
61
|
-
source = lookup_vertex(g, source_label, false);
|
62
|
-
dest = lookup_vertex(g, dest_label, false);
|
63
|
-
|
64
|
-
if (source == NULL || dest == NULL) {
|
65
|
-
return Qnil;
|
66
|
-
} else {
|
67
|
-
return INT2NUM(shortest_path(g, source, dest, best_path));
|
68
|
-
}
|
69
|
-
}
|
70
|
-
|
71
|
-
//////////////////////////////////////////////////////////////////////////////////////
|
72
|
-
|
73
|
-
void free_graph(void *data) {
|
74
|
-
Graph g = (Graph)data;
|
75
|
-
|
76
|
-
struct EdgeListStruct **vertices;
|
77
|
-
|
78
|
-
free_vertex_list(g->vertices, free_vertex);
|
79
|
-
free(g->vertex_lookup);
|
80
|
-
free(g);
|
81
|
-
}
|
82
|
-
|
83
|
-
void free_vertex(Vertex n) {
|
84
|
-
free_edge_list(n->edges, free_edge);
|
85
|
-
free(n);
|
86
|
-
}
|
87
|
-
|
88
|
-
void free_vertex_list(VertexList vertices, void (*free_item)(Vertex)) {
|
89
|
-
VertexList tmp;
|
90
|
-
while (vertices != NULL) {
|
91
|
-
tmp = vertices;
|
92
|
-
vertices = vertices->next;
|
93
|
-
if (free_item) {
|
94
|
-
free_item(tmp->vertex);
|
95
|
-
}
|
96
|
-
free(tmp);
|
97
|
-
}
|
98
|
-
}
|
99
|
-
|
100
|
-
void free_edge(Edge e) {
|
101
|
-
// Assume source and destination vertices were allocated elsewhere and will be free'd elsewhere
|
102
|
-
free(e);
|
103
|
-
}
|
104
|
-
|
105
|
-
void free_edge_list(EdgeList edges, void (*free_item)(Edge)) {
|
106
|
-
EdgeList tmp;
|
107
|
-
while (edges != NULL) {
|
108
|
-
tmp = edges;
|
109
|
-
edges = edges->next;
|
110
|
-
if (free_item) {
|
111
|
-
free_item(tmp->edge);
|
112
|
-
}
|
113
|
-
free(tmp);
|
114
|
-
}
|
115
|
-
}
|
116
|
-
|
117
|
-
//////////////////////////////////////////////////////////////////////////////////////
|
118
|
-
|
119
|
-
Vertex add_vertex(Graph g, VALUE label) {
|
120
|
-
VertexList tmp = malloc(VERTEX_LIST_SIZE);
|
121
|
-
|
122
|
-
tmp->vertex = malloc(VERTEX_SIZE);
|
123
|
-
tmp->vertex->id = g->vertices != NULL ? g->vertices->vertex->id + 1 : 0; // Auto-incrementing id
|
124
|
-
tmp->vertex->label = label;
|
125
|
-
tmp->vertex->edges = NULL;
|
126
|
-
|
127
|
-
tmp->next = g->vertices;
|
128
|
-
g->vertices = tmp;
|
129
|
-
g->vertex_count += 1;
|
130
|
-
|
131
|
-
return tmp->vertex;
|
132
|
-
}
|
133
|
-
|
134
|
-
VertexList add_vertex_to_list(VertexList list, VALUE label) {
|
135
|
-
VertexList tmp = malloc(VERTEX_LIST_SIZE);
|
136
|
-
|
137
|
-
tmp->vertex = malloc(VERTEX_SIZE);
|
138
|
-
tmp->vertex->label = label;
|
139
|
-
tmp->vertex->edges = NULL;
|
140
|
-
|
141
|
-
tmp->next = list;
|
142
|
-
return tmp;
|
143
|
-
}
|
144
|
-
|
145
|
-
Edge add_edge(Vertex source, Vertex dest, int distance) {
|
146
|
-
EdgeList tmp = malloc(EDGE_LIST_SIZE);
|
147
|
-
|
148
|
-
tmp->edge = malloc(EDGE_SIZE);
|
149
|
-
tmp->edge->source = source;
|
150
|
-
tmp->edge->dest = dest;
|
151
|
-
tmp->edge->distance = distance;
|
152
|
-
|
153
|
-
tmp->next = source->edges;
|
154
|
-
source->edges = tmp;
|
155
|
-
|
156
|
-
return tmp->edge;
|
157
|
-
}
|
158
|
-
|
159
|
-
Edge add_edge_with_labels(Graph g, VALUE source_label, VALUE dest_label, int distance) {
|
160
|
-
Vertex source, dest;
|
161
|
-
|
162
|
-
source = lookup_vertex(g, source_label, true);
|
163
|
-
dest = lookup_vertex(g, dest_label, true);
|
164
|
-
|
165
|
-
return add_edge(source, dest, distance);
|
166
|
-
}
|
167
|
-
|
168
|
-
Vertex lookup_vertex(Graph g, VALUE label, bool create_if_missing) {
|
169
|
-
Vertex n = NULL;
|
170
|
-
|
171
|
-
if (!st_lookup(g->vertex_lookup, (st_data_t)label, (st_data_t *)&n)) {
|
172
|
-
if (!create_if_missing) return NULL;
|
173
|
-
n = add_vertex(g, label);
|
174
|
-
st_add_direct(g->vertex_lookup, (st_data_t)label, (st_data_t)n);
|
175
|
-
}
|
176
|
-
return n;
|
177
|
-
}
|
178
|
-
|
179
|
-
//////////////////////////////////////////////////////////////////////////////////////
|
180
|
-
|
181
|
-
int shortest_path(Graph g, Vertex source, Vertex dest, VALUE best_path) {
|
182
|
-
Vertex *items, *prevs;
|
183
|
-
PrioritizedItemList list;
|
184
|
-
|
185
|
-
int d, du, dv;
|
186
|
-
Vertex u, v;
|
187
|
-
VertexList vl;
|
188
|
-
EdgeList el;
|
189
|
-
bool reached = source == dest;
|
190
|
-
|
191
|
-
items = malloc(g->vertex_count * sizeof(Vertex));
|
192
|
-
prevs = malloc(g->vertex_count * sizeof(Vertex));
|
193
|
-
list = make_prioritized_item_list(g->vertex_count);
|
194
|
-
|
195
|
-
for (vl = g->vertices; vl != NULL; vl = vl->next) {
|
196
|
-
v = vl->vertex;
|
197
|
-
items[v->id] = v;
|
198
|
-
prevs[v->id] = NULL;
|
199
|
-
}
|
200
|
-
|
201
|
-
update_prioritized_item(list, source->id, 0);
|
202
|
-
|
203
|
-
while (!empty_prioritized_item_list(list)) {
|
204
|
-
u = items[next_prioritized_item(list)];
|
205
|
-
du = get_priority(list, u->id);
|
206
|
-
for (el = u->edges; el != NULL; el = el->next) {
|
207
|
-
v = el->edge->dest;
|
208
|
-
dv = get_priority(list, v->id);
|
209
|
-
d = du + el->edge->distance;
|
210
|
-
if (d < 0) d = INT_MAX; // Wrapped around
|
211
|
-
|
212
|
-
if (in_prioritized_item_list(list, v->id) && d < dv) {
|
213
|
-
update_prioritized_item(list, v->id, d);
|
214
|
-
prevs[v->id] = u;
|
215
|
-
reached = reached || v == dest;
|
216
|
-
}
|
217
|
-
}
|
218
|
-
}
|
219
|
-
|
220
|
-
if (reached) {
|
221
|
-
for (v = dest; v != NULL; v = prevs[v->id]) {
|
222
|
-
rb_ary_unshift(best_path, v->label);
|
223
|
-
}
|
224
|
-
d = get_priority(list, dest->id);
|
225
|
-
} else {
|
226
|
-
d = -1;
|
227
|
-
}
|
228
|
-
|
229
|
-
free(items);
|
230
|
-
free(prevs);
|
231
|
-
free_prioritized_item_list(list);
|
232
|
-
|
233
|
-
return d;
|
234
|
-
}
|