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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30660dd68011ab507e8372a955f1d90ece8747671e14fa93bddf94c56e1ef3f3
|
4
|
+
data.tar.gz: ff92f18155e77f8d027ae4a25d94110f43e1b6d819618314391ac54cd1e6cbe7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 370902a6b3e98b591544ccd46db0cfb42b8aa5f839013110314cd3502edb55272baf69d6e50bddb890ed84e94127d642fbe127efd3906ebecb04ede6fb699fb8
|
7
|
+
data.tar.gz: d0f633f8875acad390fa3ecc4e0f4b55379a7a407160672f2a971a04944e4ac5019b144483bebe061b24c07a3d7ac13e0b4756c31b4f658da1261ff4c977b0a2
|
data/.yardopts
ADDED
data/README.md
CHANGED
@@ -16,13 +16,25 @@
|
|
16
16
|
## Description
|
17
17
|
|
18
18
|
[Dijkstra](https://en.wikipedia.org/wiki/Dijkstra's_algorithm) is a commonly
|
19
|
-
used algorithm for finding the shortest path
|
19
|
+
used algorithm for finding the shortest path/distance between two items in an
|
20
|
+
interconnected collection of items (such as a graph or network).
|
20
21
|
|
21
22
|
|
22
23
|
## Features
|
23
24
|
|
24
25
|
* Native implementation of Dijkstra's algorithm intended for use on large,
|
25
|
-
sparse graphs
|
26
|
+
typically sparse graphs.
|
27
|
+
* Allows for calculating shortest distance without requiring all nodes/edges to
|
28
|
+
be produced. (In many applications doing this can be more expensive than
|
29
|
+
Dijkstra's algorithm itself - or even impractical.)
|
30
|
+
* Can be used in one of three ways:
|
31
|
+
* __Method 1__: Use an instance of `DijkstraFast::Graph` by adding edges to
|
32
|
+
it.
|
33
|
+
* __Method 2__: Add `shortest_path` and `shortest_distance` methods to an
|
34
|
+
existing class by including the `DijkstraFast::ShortestPath` module.
|
35
|
+
* __Method 3__: Call `DijkstraFast::ShortestPath.shortest_path` or
|
36
|
+
`DijkstraFast::ShortestPath.shortest_distance` directly when the `source` and
|
37
|
+
`dest` objects know how to find "connected" items via some method.
|
26
38
|
|
27
39
|
|
28
40
|
## Installation
|
@@ -35,27 +47,109 @@ gem install dijkstra_fast
|
|
35
47
|
|
36
48
|
* Ruby 3.0 or higher
|
37
49
|
|
50
|
+
|
38
51
|
## Usage
|
39
52
|
|
40
|
-
|
53
|
+
### Method 1: Use `DijkstraFast::Graph`
|
41
54
|
|
42
55
|
```ruby
|
43
|
-
require 'dijkstra_fast'
|
44
|
-
|
45
56
|
graph = DijkstraFast::Graph.new
|
46
57
|
graph.add("A", "B", distance: 5)
|
47
58
|
graph.add("A", "C", distance: 8)
|
48
59
|
graph.add("B", "C", distance: 2)
|
49
|
-
|
50
|
-
|
60
|
+
distance, path = graph.shortest_path("A", "C")
|
61
|
+
path
|
51
62
|
|
52
63
|
=> ["A", "B", "C"]
|
53
64
|
|
54
|
-
|
65
|
+
distance
|
55
66
|
|
56
67
|
=> 7
|
57
68
|
```
|
58
69
|
|
70
|
+
### Method 2: Include `DijkstraFast::ShortestPath`
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class MyNetwork
|
74
|
+
include DijkstraFast::ShortestPath
|
75
|
+
|
76
|
+
def connections(source)
|
77
|
+
case source
|
78
|
+
when "A"
|
79
|
+
yield "B", 3
|
80
|
+
yield "C", 8
|
81
|
+
when "B"
|
82
|
+
yield "C" # Default distance 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
distance, path = MyNetwork.new.shortest_path("A", "C")
|
88
|
+
path
|
89
|
+
|
90
|
+
=> ["A", "B", "C"]
|
91
|
+
|
92
|
+
distance
|
93
|
+
|
94
|
+
=> 4
|
95
|
+
```
|
96
|
+
|
97
|
+
### Method 3: Call `DijkstraFast.shortest_path` or `DijkstraFast.shortest_distance`
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
Node = Struct.new(:label) do
|
101
|
+
def connections
|
102
|
+
case label
|
103
|
+
when "A"
|
104
|
+
yield Node.new("B"), 5
|
105
|
+
yield Node.new("C"), 8
|
106
|
+
when "B"
|
107
|
+
yield Node.new("C"), 2
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
a = Node.new("A")
|
113
|
+
b = Node.new("B")
|
114
|
+
c = Node.new("C")
|
115
|
+
|
116
|
+
distance, path = DijkstraFast.shortest_path(a, c)
|
117
|
+
path.map(&:label)
|
118
|
+
|
119
|
+
=> ["A", "B", "C"]
|
120
|
+
|
121
|
+
distance
|
122
|
+
|
123
|
+
=> 7
|
124
|
+
```
|
125
|
+
|
126
|
+
|
127
|
+
## Additional Notes
|
128
|
+
|
129
|
+
* When using arbitrary Ruby objects as graph nodes, it is important to ensure
|
130
|
+
that [Object#hash](https://ruby-doc.org/core-3.1.0/Object.html#method-i-hash)
|
131
|
+
and [Object#eql?](https://ruby-doc.org/core-3.1.0/Object.html#method-i-eql-3F)
|
132
|
+
are properly implemented so that two instances which are logically the same
|
133
|
+
will be considered as the same node. Under the hood, this gem creates a
|
134
|
+
bi-directional mapping between nodes and an auto-incrementing integer id so
|
135
|
+
that Ruby objects do not have to be passed into the internals of the native
|
136
|
+
implementation; doing so would risk problems with garbage collection and is
|
137
|
+
otherwise frowned upon when implementing native extensions.
|
138
|
+
* All `shortest_path` and `shortest_distance` methods provide an optional
|
139
|
+
`progress` named argument which (when set to anything truthy) will provide
|
140
|
+
progress as the algorithm proceeds. The default implementation uses the
|
141
|
+
[progressbar](https://github.com/jfelchner/ruby-progressbar/wiki) gem, but this
|
142
|
+
is not required.
|
143
|
+
* The maximum number of items that can be handled by this implementation is
|
144
|
+
determined by the size of `unsigned long` which is compiler dependent. On many
|
145
|
+
machines this can be 18,446,744,073,709,551,615 (2^64 – 1). A
|
146
|
+
[RuntimeError](https://ruby-doc.org/core-3.1.0/RuntimeError.html) will be
|
147
|
+
thrown if this is surpassed; if so, congratulations on being bad ass!
|
148
|
+
* A priority queue is used within the native Dijkstra implemenation to maintain
|
149
|
+
the next shortest path to consider within the algorithm's main loop. It is
|
150
|
+
possible that switching to a [Fibonacci
|
151
|
+
heap](https://en.wikipedia.org/wiki/Fibonacci_heap) might improve performance.
|
152
|
+
|
59
153
|
|
60
154
|
## License
|
61
155
|
|
data/Rakefile
CHANGED
@@ -1,16 +1,13 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/extensiontask'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'yard'
|
4
5
|
|
5
6
|
RSpec::Core::RakeTask.new(:spec)
|
6
7
|
|
7
|
-
task :
|
8
|
+
task default: :spec
|
8
9
|
|
9
|
-
|
10
|
-
RDoc::Task.new do |rdoc|
|
11
|
-
rdoc.main = "README.md"
|
12
|
-
rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
|
13
|
-
end
|
10
|
+
YARD::Rake::YardocTask.new
|
14
11
|
|
15
12
|
Rake::ExtensionTask.new('dijkstra_fast') do |ext|
|
16
13
|
ext.lib_dir = 'lib/dijkstra_fast'
|
data/dijkstra_fast.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
lib = File.expand_path('
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
3
|
require 'dijkstra_fast/version'
|
4
4
|
|
@@ -10,13 +10,13 @@ Gem::Specification.new do |spec|
|
|
10
10
|
|
11
11
|
spec.summary = '(Native) implementation of Dijkstra algorithm for large, sparse graphs'
|
12
12
|
spec.description = <<~DESCRIPTION
|
13
|
-
Native implementation of Dijkstra algorithm for finding the shortest path
|
14
|
-
between two vertices in a large, sparse graphs. Underlying algorithm is
|
15
|
-
implemented in C using a priority queue. Edges are represented using linked
|
16
|
-
lists rather than an adjacency matrix to reduce memory footprint when operating
|
17
|
-
on very large graphs where the average number of edges between nodes is
|
18
|
-
relatively small (e.g. < 1/10 the number of nodes). See
|
19
|
-
https://en.wikipedia.org/wiki/Dijkstra's_algorithm for additional information.
|
13
|
+
Native implementation of Dijkstra algorithm for finding the shortest path
|
14
|
+
between two vertices in a large, sparse graphs. Underlying algorithm is
|
15
|
+
implemented in C using a priority queue. Edges are represented using linked
|
16
|
+
lists rather than an adjacency matrix to reduce memory footprint when operating
|
17
|
+
on very large graphs where the average number of edges between nodes is
|
18
|
+
relatively small (e.g. < 1/10 the number of nodes). See
|
19
|
+
https://en.wikipedia.org/wiki/Dijkstra's_algorithm for additional information.
|
20
20
|
DESCRIPTION
|
21
21
|
spec.homepage = 'https://github.com/david-mccullars/dijkstra_fast'
|
22
22
|
spec.license = 'MIT'
|
@@ -26,14 +26,20 @@ https://en.wikipedia.org/wiki/Dijkstra's_algorithm for additional information.
|
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
27
|
spec.extensions = ['ext/dijkstra_fast/extconf.rb']
|
28
28
|
spec.require_paths = ['lib']
|
29
|
+
spec.required_ruby_version = '>= 3.0.0'
|
29
30
|
|
30
31
|
spec.add_development_dependency 'bundler'
|
32
|
+
spec.add_development_dependency 'github-markup'
|
31
33
|
spec.add_development_dependency 'rake'
|
32
34
|
spec.add_development_dependency 'rake-compiler'
|
35
|
+
spec.add_development_dependency 'redcarpet'
|
33
36
|
spec.add_development_dependency 'rspec'
|
34
37
|
spec.add_development_dependency 'rubocop'
|
35
38
|
spec.add_development_dependency 'rubocop-rake'
|
36
39
|
spec.add_development_dependency 'rubocop-rspec'
|
37
40
|
spec.add_development_dependency 'simplecov'
|
38
41
|
spec.add_development_dependency 'yard'
|
42
|
+
spec.metadata = {
|
43
|
+
'rubygems_mfa_required' => 'true',
|
44
|
+
}
|
39
45
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <errors.h>
|
3
|
+
|
4
|
+
void raise_max_capacity_reached_error(const char *expandable) {
|
5
|
+
rb_raise(rb_eRuntimeError, "Maximum capacity reached for %s", expandable);
|
6
|
+
}
|
7
|
+
|
8
|
+
void raise_no_path_error() {
|
9
|
+
VALUE DijkstraFastModule = rb_const_get(rb_cObject, rb_intern("DijkstraFast"));
|
10
|
+
VALUE NoPathExistsError = rb_const_get(DijkstraFastModule, rb_intern("NoPathExistsError"));
|
11
|
+
rb_raise(NoPathExistsError, "No path exists from source to destination");
|
12
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#ifndef EXPAND_CAPACITY_H
|
2
|
+
#define EXPAND_CAPACITY_H
|
3
|
+
|
4
|
+
#define expand_capacity(TYPE, ITEMS, CAPACITY, INITIAL_CAPACITY, DESC) { \
|
5
|
+
unsigned long prev_capacity = CAPACITY; \
|
6
|
+
TYPE *prev_items = ITEMS; \
|
7
|
+
\
|
8
|
+
if (CAPACITY == UINT_MAX) raise_max_capacity_reached_error(DESC); \
|
9
|
+
CAPACITY = prev_items == NULL ? INITIAL_CAPACITY : prev_capacity * 2; \
|
10
|
+
if (CAPACITY <= prev_capacity) CAPACITY = UINT_MAX; \
|
11
|
+
ITEMS = calloc(CAPACITY, sizeof(TYPE)); \
|
12
|
+
\
|
13
|
+
if (prev_items != NULL) { \
|
14
|
+
memcpy(ITEMS, prev_items, prev_capacity * sizeof(TYPE)); \
|
15
|
+
free(prev_items); \
|
16
|
+
} \
|
17
|
+
}
|
18
|
+
|
19
|
+
#endif
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <native.h>
|
3
|
+
|
4
|
+
const size_t NATIVE_SIZE = sizeof(NativeStruct);
|
5
|
+
const size_t ITEM_DATA_SIZE = sizeof(ItemDataStruct);
|
6
|
+
|
7
|
+
//////////////////////////////////////////////////////////////////////////////////////
|
8
|
+
|
9
|
+
Native make_native() {
|
10
|
+
Native n = malloc(NATIVE_SIZE);
|
11
|
+
n->pq = make_priority_queue();
|
12
|
+
n->item_data_capacity = 0;
|
13
|
+
n->item_data = NULL;
|
14
|
+
n->u = NULL_ITEM;
|
15
|
+
n->completed = 0;
|
16
|
+
expand_item_data(n);
|
17
|
+
return n;
|
18
|
+
}
|
19
|
+
|
20
|
+
void free_native(void *data) {
|
21
|
+
Native n = (Native)data;
|
22
|
+
free_priority_queue(n->pq);
|
23
|
+
for (unsigned long i = 0; i < n->item_data_capacity; i++) {
|
24
|
+
if (n->item_data[i] != NULL) free_item_data(n->item_data[i]);
|
25
|
+
}
|
26
|
+
free(n->item_data);
|
27
|
+
free(n);
|
28
|
+
}
|
29
|
+
|
30
|
+
ItemData make_item_data() {
|
31
|
+
ItemData id = malloc(ITEM_DATA_SIZE);
|
32
|
+
id->previous = 0;
|
33
|
+
id->distance = PRIORITY_MAX;
|
34
|
+
id->visited = false;
|
35
|
+
return id;
|
36
|
+
}
|
37
|
+
|
38
|
+
void free_item_data(ItemData id) {
|
39
|
+
free(id);
|
40
|
+
}
|
41
|
+
|
42
|
+
//////////////////////////////////////////////////////////////////////////////////////
|
43
|
+
|
44
|
+
ItemData get_item_data(Native n, ITEM u, bool create_if_not_exists) {
|
45
|
+
if (create_if_not_exists) {
|
46
|
+
while (n->item_data_capacity <= u) expand_item_data(n);
|
47
|
+
if (n->item_data[u] == NULL) n->item_data[u] = make_item_data();
|
48
|
+
}
|
49
|
+
return n->item_data_capacity <= u ? NULL : n->item_data[u];
|
50
|
+
}
|
51
|
+
|
52
|
+
PRIORITY get_distance(Native n, ITEM u) {
|
53
|
+
ItemData uid = get_item_data(n, u, false);
|
54
|
+
return uid == NULL ? PRIORITY_MAX : uid->distance;
|
55
|
+
}
|
56
|
+
|
57
|
+
void update_distance(Native n, ITEM u, PRIORITY distance) {
|
58
|
+
ItemData uid = get_item_data(n, u, true);
|
59
|
+
uid->distance = distance;
|
60
|
+
}
|
61
|
+
|
62
|
+
bool has_visited(Native n, ITEM v) {
|
63
|
+
ItemData vid = get_item_data(n, v, false);
|
64
|
+
return vid == NULL ? false : vid->visited;
|
65
|
+
}
|
66
|
+
|
67
|
+
void mark_visited(Native n, ITEM v) {
|
68
|
+
ItemData vid = get_item_data(n, v, true);
|
69
|
+
vid->visited = true;
|
70
|
+
}
|
71
|
+
|
72
|
+
ITEM get_previous(Native n, ITEM v) {
|
73
|
+
ItemData vid = get_item_data(n, v, false);
|
74
|
+
return vid == NULL ? 0 : vid->previous;
|
75
|
+
}
|
76
|
+
|
77
|
+
void update_previous(Native n, ITEM v, ITEM u) {
|
78
|
+
ItemData vid = get_item_data(n, v, true);
|
79
|
+
vid->previous = u;
|
80
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#ifndef NATIVE_H
|
2
|
+
#define NATIVE_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <errors.h>
|
6
|
+
#include <expand_capacity.h>
|
7
|
+
#include <priority_queue.h>
|
8
|
+
|
9
|
+
struct ItemDataStruct;
|
10
|
+
typedef struct ItemDataStruct* ItemData;
|
11
|
+
|
12
|
+
typedef struct ItemDataStruct {
|
13
|
+
ITEM previous;
|
14
|
+
PRIORITY distance;
|
15
|
+
bool visited;
|
16
|
+
} ItemDataStruct;
|
17
|
+
|
18
|
+
//////////////////////////////////////////////////////////////////////////////////////
|
19
|
+
|
20
|
+
struct NativeStruct;
|
21
|
+
typedef struct NativeStruct* Native;
|
22
|
+
|
23
|
+
typedef struct NativeStruct {
|
24
|
+
PriorityQueue pq;
|
25
|
+
ItemData *item_data;
|
26
|
+
unsigned long item_data_capacity;
|
27
|
+
ITEM u; // current working item
|
28
|
+
ITEM completed; // Number of items completed (for progress)
|
29
|
+
} NativeStruct;
|
30
|
+
|
31
|
+
//////////////////////////////////////////////////////////////////////////////////////
|
32
|
+
|
33
|
+
Native make_native();
|
34
|
+
#define expand_item_data(n) expand_capacity(ItemData, n->item_data, n->item_data_capacity, 256, "Dijkstra items")
|
35
|
+
void free_native(void *data);
|
36
|
+
|
37
|
+
ItemData make_item_data();
|
38
|
+
void free_item_data(ItemData id);
|
39
|
+
|
40
|
+
//////////////////////////////////////////////////////////////////////////////////////
|
41
|
+
|
42
|
+
void Init_native_shortest_path();
|
43
|
+
VALUE native_allocate(VALUE self);
|
44
|
+
VALUE native_shortest_path(VALUE self, VALUE source, VALUE dest, VALUE progress);
|
45
|
+
VALUE with_connection(VALUE v, VALUE context, int argc, VALUE extra[]);
|
46
|
+
VALUE to_result(Native n, ITEM source, ITEM dest);
|
47
|
+
|
48
|
+
//////////////////////////////////////////////////////////////////////////////////////
|
49
|
+
|
50
|
+
PRIORITY get_distance(Native n, ITEM u);
|
51
|
+
void update_distance(Native n, ITEM u, PRIORITY distance);
|
52
|
+
bool has_visited(Native n, ITEM v);
|
53
|
+
void mark_visited(Native n, ITEM v);
|
54
|
+
ITEM get_previous(Native n, ITEM v);
|
55
|
+
void update_previous(Native n, ITEM v, ITEM u);
|
56
|
+
|
57
|
+
#endif
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <native.h>
|
3
|
+
|
4
|
+
static const rb_data_type_t native_typed_data = {
|
5
|
+
"DijkstraFast/Native",
|
6
|
+
{ 0, free_native, },
|
7
|
+
0, 0,
|
8
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
9
|
+
};
|
10
|
+
|
11
|
+
//////////////////////////////////////////////////////////////////////////////////////
|
12
|
+
|
13
|
+
void Init_native_shortest_path() {
|
14
|
+
VALUE DijkstraFastModule = rb_const_get(rb_cObject, rb_intern("DijkstraFast"));
|
15
|
+
VALUE NativeClass = rb_const_get(DijkstraFastModule, rb_intern("Native"));
|
16
|
+
|
17
|
+
rb_define_alloc_func(NativeClass, native_allocate);
|
18
|
+
rb_define_private_method(NativeClass, "_shortest_path", native_shortest_path, 3);
|
19
|
+
}
|
20
|
+
|
21
|
+
VALUE native_allocate(VALUE self) {
|
22
|
+
Native n = make_native();
|
23
|
+
return TypedData_Wrap_Struct(self, &native_typed_data, n);
|
24
|
+
}
|
25
|
+
|
26
|
+
VALUE native_shortest_path(VALUE self, VALUE source_obj, VALUE dest_obj, VALUE progress) {
|
27
|
+
VALUE method_args[1];
|
28
|
+
Native n;
|
29
|
+
|
30
|
+
TypedData_Get_Struct(self, NativeStruct, &native_typed_data, n);
|
31
|
+
|
32
|
+
ITEM source = NUM2INT(source_obj);
|
33
|
+
ITEM dest = NUM2INT(dest_obj);
|
34
|
+
|
35
|
+
priority_queue_push(n->pq, source, 0);
|
36
|
+
update_distance(n, source, 0);
|
37
|
+
|
38
|
+
while (!(priority_queue_is_empty(n->pq))) {
|
39
|
+
n->u = priority_queue_pop(n->pq);
|
40
|
+
|
41
|
+
if (n->u == dest) break; // We're done!!!
|
42
|
+
if (has_visited(n, n->u)) continue;
|
43
|
+
|
44
|
+
mark_visited(n, n->u);
|
45
|
+
method_args[0] = INT2NUM(n->u);
|
46
|
+
rb_block_call(self, rb_intern("connections"), 1, method_args, RUBY_METHOD_FUNC(with_connection), (VALUE)n);
|
47
|
+
|
48
|
+
if (progress != Qnil) {
|
49
|
+
n->completed++;
|
50
|
+
rb_funcall(progress, rb_intern("call"), 2, INT2NUM(n->completed), INT2NUM(n->completed + n->pq->size));
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
return to_result(n, source, dest);
|
55
|
+
}
|
56
|
+
|
57
|
+
VALUE with_connection(VALUE v_obj, VALUE context, int argc, VALUE extra[]) {
|
58
|
+
Native n = (Native)context;
|
59
|
+
ITEM u = n->u;
|
60
|
+
ITEM v = NUM2INT(v_obj);
|
61
|
+
|
62
|
+
if (has_visited(n, v)) return Qnil;
|
63
|
+
|
64
|
+
PRIORITY distance = argc > 1 ? NUM2INT(extra[1]) : 1;
|
65
|
+
PRIORITY alt_distance = priority_sum(get_distance(n, u), distance);
|
66
|
+
|
67
|
+
if (alt_distance >= get_distance(n, v)) return Qnil;
|
68
|
+
|
69
|
+
priority_queue_push(n->pq, v, alt_distance);
|
70
|
+
update_distance(n, v, alt_distance);
|
71
|
+
update_previous(n, v, u);
|
72
|
+
|
73
|
+
return Qnil;
|
74
|
+
}
|
75
|
+
|
76
|
+
//////////////////////////////////////////////////////////////////////////////////////
|
77
|
+
|
78
|
+
VALUE to_result(Native n, ITEM source, ITEM dest) {
|
79
|
+
PRIORITY distance = get_distance(n, dest);
|
80
|
+
VALUE path = rb_ary_new();
|
81
|
+
|
82
|
+
if (distance == PRIORITY_MAX) raise_no_path_error();
|
83
|
+
|
84
|
+
while (dest != NULL_ITEM) {
|
85
|
+
rb_ary_unshift(path, INT2NUM(dest));
|
86
|
+
dest = dest == source ? NULL_ITEM : get_previous(n, dest);
|
87
|
+
}
|
88
|
+
|
89
|
+
return rb_ary_new3(2, INT2NUM(distance), path);
|
90
|
+
}
|
@@ -0,0 +1,109 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <priority_queue.h>
|
3
|
+
|
4
|
+
const size_t PRIORITY_QUEUE_SIZE = sizeof(PriorityQueueStruct);
|
5
|
+
|
6
|
+
//////////////////////////////////////////////////////////////////////////////////////
|
7
|
+
|
8
|
+
void swap_prioritized_items(PrioritizedItem *items, unsigned long i, unsigned long j) {
|
9
|
+
PrioritizedItem tmp = items[i];
|
10
|
+
items[i] = items[j];
|
11
|
+
items[j] = tmp;
|
12
|
+
}
|
13
|
+
|
14
|
+
void reprioritize_right(PrioritizedItem *items, unsigned long i, unsigned long size) {
|
15
|
+
unsigned long orig_i = i;
|
16
|
+
PRIORITY i_priority, j_priority_left, j_priority_right;
|
17
|
+
unsigned long j_left, j_right;
|
18
|
+
i_priority = items[i]->priority;
|
19
|
+
|
20
|
+
while (true) {
|
21
|
+
j_left = (i << 1) + 1;
|
22
|
+
j_right = j_left + 1;
|
23
|
+
|
24
|
+
j_priority_left = j_left < size ? items[j_left]->priority : PRIORITY_MAX;
|
25
|
+
j_priority_right = j_right < size ? items[j_right]->priority : PRIORITY_MAX;
|
26
|
+
|
27
|
+
if (j_priority_right < i_priority && j_priority_right < j_priority_left) {
|
28
|
+
swap_prioritized_items(items, i, j_right);
|
29
|
+
i = j_right;
|
30
|
+
|
31
|
+
} else if (j_priority_left < i_priority) {
|
32
|
+
swap_prioritized_items(items, i, j_left);
|
33
|
+
i = j_left;
|
34
|
+
|
35
|
+
} else {
|
36
|
+
return;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
void reprioritize_left(PrioritizedItem *items, unsigned long i) {
|
42
|
+
PRIORITY i_priority, j_priority;
|
43
|
+
unsigned long j;
|
44
|
+
i_priority = items[i]->priority;
|
45
|
+
|
46
|
+
while (i > 0) {
|
47
|
+
j = (i - 1) >> 1;
|
48
|
+
j_priority = items[j]->priority;
|
49
|
+
|
50
|
+
if (j_priority > i_priority) {
|
51
|
+
swap_prioritized_items(items, i, j);
|
52
|
+
i = j;
|
53
|
+
|
54
|
+
} else {
|
55
|
+
return;
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
//////////////////////////////////////////////////////////////////////////////////////
|
61
|
+
|
62
|
+
PriorityQueue make_priority_queue() {
|
63
|
+
PriorityQueue pq = malloc(PRIORITY_QUEUE_SIZE);
|
64
|
+
pq->size = 0;
|
65
|
+
pq->capacity = 0;
|
66
|
+
pq->items = NULL;
|
67
|
+
expand_priority_queue(pq);
|
68
|
+
return pq;
|
69
|
+
}
|
70
|
+
|
71
|
+
void free_priority_queue(PriorityQueue pq) {
|
72
|
+
for (unsigned long i = 0; i < pq->size; i++) free(pq->items[i]);
|
73
|
+
free(pq->items);
|
74
|
+
free(pq);
|
75
|
+
}
|
76
|
+
|
77
|
+
bool priority_queue_is_empty(PriorityQueue pq) {
|
78
|
+
return pq->size == 0 ? Qtrue : Qfalse;
|
79
|
+
}
|
80
|
+
|
81
|
+
void priority_queue_push(PriorityQueue pq, ITEM object, PRIORITY priority) {
|
82
|
+
PrioritizedItem item = malloc(sizeof(PrioritizedItem));
|
83
|
+
item->item = object;
|
84
|
+
item->priority = priority;
|
85
|
+
|
86
|
+
if (pq->size == pq->capacity) expand_priority_queue(pq);
|
87
|
+
pq->items[pq->size] = item;
|
88
|
+
reprioritize_left(pq->items, pq->size);
|
89
|
+
pq->size++;
|
90
|
+
}
|
91
|
+
|
92
|
+
ITEM priority_queue_pop(PriorityQueue pq) {
|
93
|
+
if (pq->size == 0) return 0;
|
94
|
+
|
95
|
+
swap_prioritized_items(pq->items, 0, pq->size - 1);
|
96
|
+
pq->size--;
|
97
|
+
reprioritize_right(pq->items, 0, pq->size);
|
98
|
+
|
99
|
+
ITEM item = pq->items[pq->size]->item;
|
100
|
+
free(pq->items[pq->size]);
|
101
|
+
|
102
|
+
return item;
|
103
|
+
}
|
104
|
+
|
105
|
+
// Safely sum two priorities without overflow
|
106
|
+
PRIORITY priority_sum(PRIORITY p1, PRIORITY p2) {
|
107
|
+
PRIORITY sum = p1 + p2;
|
108
|
+
return sum < p1 || sum < p2 ? PRIORITY_MAX : sum;
|
109
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#ifndef PRIORITY_QUEUE_H
|
2
|
+
#define PRIORITY_QUEUE_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <errors.h>
|
6
|
+
#include <expand_capacity.h>
|
7
|
+
|
8
|
+
typedef unsigned long ITEM;
|
9
|
+
typedef unsigned int PRIORITY;
|
10
|
+
static const PRIORITY PRIORITY_MAX = UINT_MAX;
|
11
|
+
static const ITEM NULL_ITEM = 0;
|
12
|
+
|
13
|
+
struct PrioritizedItemStruct;
|
14
|
+
typedef struct PrioritizedItemStruct* PrioritizedItem;
|
15
|
+
|
16
|
+
typedef struct PrioritizedItemStruct {
|
17
|
+
ITEM item;
|
18
|
+
PRIORITY priority;
|
19
|
+
} PrioritizedItemStruct;
|
20
|
+
|
21
|
+
//////////////////////////////////////////////////////////////////////////////////////
|
22
|
+
|
23
|
+
struct PriorityQueueStruct;
|
24
|
+
typedef struct PriorityQueueStruct* PriorityQueue;
|
25
|
+
|
26
|
+
typedef struct PriorityQueueStruct {
|
27
|
+
PrioritizedItem *items;
|
28
|
+
unsigned long capacity;
|
29
|
+
unsigned long size;
|
30
|
+
} PriorityQueueStruct;
|
31
|
+
|
32
|
+
//////////////////////////////////////////////////////////////////////////////////////
|
33
|
+
|
34
|
+
PriorityQueue make_priority_queue();
|
35
|
+
#define expand_priority_queue(pq) expand_capacity(PrioritizedItem, pq->items, pq->capacity, 256, "Priority Queue")
|
36
|
+
void free_priority_queue(PriorityQueue pq);
|
37
|
+
bool priority_queue_is_empty(PriorityQueue pq);
|
38
|
+
void priority_queue_push(PriorityQueue pq, ITEM object, PRIORITY priority);
|
39
|
+
ITEM priority_queue_pop(PriorityQueue pq);
|
40
|
+
PRIORITY priority_sum(PRIORITY p1, PRIORITY p2);
|
41
|
+
|
42
|
+
#endif
|