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
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
|