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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0806b283afa40238d914c51d06c587a4dc053d65431bb68c55b6a06a827bb245'
4
- data.tar.gz: c070c8d2f5bfb38b3fe25469190ef460ca1581a3c670b1715cb3696b6e46373b
3
+ metadata.gz: 30660dd68011ab507e8372a955f1d90ece8747671e14fa93bddf94c56e1ef3f3
4
+ data.tar.gz: ff92f18155e77f8d027ae4a25d94110f43e1b6d819618314391ac54cd1e6cbe7
5
5
  SHA512:
6
- metadata.gz: 04640eac7496a7fd98dae710d4582f27397b86f4782e9c6fec3eb4a4ef4d46aff809c33a7295dee419c56f09a276214937b09beb1a03220bcbcf3c65d977e2d6
7
- data.tar.gz: ee26aad2a4a9ab681754e4b8682f8b465e94bc36797da88f3d2ef62de260f4e69e9f2c80dd6856962f3e80a88f3e31cc3e871d9ac9d5d0146a6cfb10906856c7
6
+ metadata.gz: 370902a6b3e98b591544ccd46db0cfb42b8aa5f839013110314cd3502edb55272baf69d6e50bddb890ed84e94127d642fbe127efd3906ebecb04ede6fb699fb8
7
+ data.tar.gz: d0f633f8875acad390fa3ecc4e0f4b55379a7a407160672f2a971a04944e4ac5019b144483bebe061b24c07a3d7ac13e0b4756c31b4f658da1261ff4c977b0a2
data/.yardopts ADDED
@@ -0,0 +1,7 @@
1
+ --readme README.md
2
+ --title 'Dijkstra (Fast!) Documentation'
3
+ --charset utf-8
4
+ --markup-provider=redcarpet
5
+ --markup markdown
6
+ --exclude lib/dijkstra_fast/native.rb
7
+ 'lib/**/*.rb' - '*.md'
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 through a graph or network.
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 for which an array of arrays is inefficient.
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
- **Dijkstra**
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
- best_path = graph.shortest_path("A", "C")
50
- best_path.path
60
+ distance, path = graph.shortest_path("A", "C")
61
+ path
51
62
 
52
63
  => ["A", "B", "C"]
53
64
 
54
- best_path.distance
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 "bundler/gem_tasks"
2
- require "rake/extensiontask"
3
- require "rspec/core/rake_task"
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 :default => :spec
8
+ task default: :spec
8
9
 
9
- require 'rdoc/task'
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'
@@ -1,4 +1,4 @@
1
- lib = File.expand_path('../lib', __FILE__)
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
@@ -1,5 +1,5 @@
1
- #include <dijkstra_graph.h>
1
+ #include <native.h>
2
2
 
3
3
  void Init_dijkstra_fast() {
4
- Init_dijkstra_graph();
4
+ Init_native_shortest_path();
5
5
  }
@@ -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,7 @@
1
+ #ifndef ERRORS_H
2
+ #define ERRORS_H
3
+
4
+ void raise_max_capacity_reached_error(const char *expandable);
5
+ void raise_no_path_error();
6
+
7
+ #endif
@@ -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