dijkstra_fast 1.4.2 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
}
|