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