d_heap 0.2.2 → 0.6.1

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.
@@ -0,0 +1,392 @@
1
+ Profiling run at 2021-01-10 21:34:56 -0500
2
+ ruby v2.7.2, DHeap v0.3.0
3
+
4
+ ~~~~~~ filling @dheap_bm_random_vals with 1.0M ~~~~~~
5
+ ########################################################################
6
+ # Profile w/ N=5 (i=1000000)
7
+ # (n.b. RubyProf & tracepoint can change relative performance.
8
+ # A sampling profiler can provide more accurate relative metrics.
9
+ ########################################################################
10
+
11
+ Filling push and resort ---------------------------
12
+ Profiling push and resort ---------------------------
13
+ Measure Mode: wall_time
14
+ Thread ID: 1360
15
+ Fiber ID: 1340
16
+ Total: 2.639873
17
+ Sort by: self_time
18
+
19
+ %self total self wait child calls name location
20
+ 31.49 2.640 0.831 0.000 1.808 1 DHeap::Benchmarks::Scenarios#repeated_push_pop /home/nick/src/d_heap/lib/d_heap/benchmarks.rb:81
21
+ 25.69 1.219 0.678 0.000 0.541 1000000 DHeap::Benchmarks::Sorting#<< /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:35
22
+ 15.00 0.396 0.396 0.000 0.000 1000000 Array#sort!
23
+ 12.38 0.462 0.327 0.000 0.136 1000000 DHeap::Benchmarks::Sorting#pop /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:42
24
+ 5.50 0.145 0.145 0.000 0.000 1000000 Array#push
25
+ 5.13 0.136 0.136 0.000 0.000 1000000 Array#shift
26
+ 4.81 0.127 0.127 0.000 0.000 1000000 Array#fetch
27
+
28
+ * recursively called methods
29
+
30
+ Columns are:
31
+
32
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
33
+ total - The time spent in this method and its children.
34
+ self - The time spent in this method.
35
+ wait - The amount of time this method waited for other threads.
36
+ child - The time spent in this method's children.
37
+ calls - The number of times this method was called.
38
+ name - The name of the method.
39
+ location - The location of the method.
40
+
41
+ The interpretation of method names is:
42
+
43
+ * MyObject#test - An instance method "test" of the class "MyObject"
44
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
45
+
46
+ Filling bsearch + insert ---------------------------
47
+ Profiling bsearch + insert ---------------------------
48
+ Measure Mode: wall_time
49
+ Thread ID: 1360
50
+ Fiber ID: 1340
51
+ Total: 2.634233
52
+ Sort by: self_time
53
+
54
+ %self total self wait child calls name location
55
+ 30.73 2.634 0.810 0.000 1.825 1 DHeap::Benchmarks::Scenarios#repeated_push_pop /home/nick/src/d_heap/lib/d_heap/benchmarks.rb:81
56
+ 25.94 1.228 0.683 0.000 0.545 1000000 DHeap::Benchmarks::BSearch#<< /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:61
57
+ 14.83 0.391 0.391 0.000 0.000 1000000 Array#bsearch_index
58
+ 12.79 0.462 0.337 0.000 0.125 1000000 DHeap::Benchmarks::BSearch#pop /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:70
59
+ 5.85 0.154 0.154 0.000 0.000 1000000 Array#insert
60
+ 5.10 0.134 0.134 0.000 0.000 1000000 Array#fetch
61
+ 4.76 0.125 0.125 0.000 0.000 1000000 Array#pop
62
+
63
+ * recursively called methods
64
+
65
+ Columns are:
66
+
67
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
68
+ total - The time spent in this method and its children.
69
+ self - The time spent in this method.
70
+ wait - The amount of time this method waited for other threads.
71
+ child - The time spent in this method's children.
72
+ calls - The number of times this method was called.
73
+ name - The name of the method.
74
+ location - The location of the method.
75
+
76
+ The interpretation of method names is:
77
+
78
+ * MyObject#test - An instance method "test" of the class "MyObject"
79
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
80
+
81
+ Filling ruby binary heap ---------------------------
82
+ Profiling ruby binary heap ---------------------------
83
+ Measure Mode: wall_time
84
+ Thread ID: 1360
85
+ Fiber ID: 1340
86
+ Total: 4.940103
87
+ Sort by: self_time
88
+
89
+ %self total self wait child calls name location
90
+ 37.38 1.973 1.846 0.000 0.126 1000000 DHeap::Benchmarks::RbHeap#<< /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:80
91
+ 33.66 2.042 1.663 0.000 0.380 1000000 DHeap::Benchmarks::RbHeap#pop /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:96
92
+ 16.10 4.940 0.795 0.000 4.145 1 DHeap::Benchmarks::Scenarios#repeated_push_pop /home/nick/src/d_heap/lib/d_heap/benchmarks.rb:81
93
+ 2.64 0.130 0.130 0.000 0.000 1000000 Array#pop
94
+ 2.63 0.130 0.130 0.000 0.000 1000000 Array#first
95
+ 2.62 0.130 0.130 0.000 0.000 1000000 Array#fetch
96
+ 2.56 0.126 0.126 0.000 0.000 1000000 Array#push
97
+ 2.43 0.120 0.120 0.000 0.000 1000000 Array#last
98
+
99
+ * recursively called methods
100
+
101
+ Columns are:
102
+
103
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
104
+ total - The time spent in this method and its children.
105
+ self - The time spent in this method.
106
+ wait - The amount of time this method waited for other threads.
107
+ child - The time spent in this method's children.
108
+ calls - The number of times this method was called.
109
+ name - The name of the method.
110
+ location - The location of the method.
111
+
112
+ The interpretation of method names is:
113
+
114
+ * MyObject#test - An instance method "test" of the class "MyObject"
115
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
116
+
117
+ Filling quaternary DHeap ---------------------------
118
+ Profiling quaternary DHeap ---------------------------
119
+ Measure Mode: wall_time
120
+ Thread ID: 1360
121
+ Fiber ID: 1340
122
+ Total: 1.231464
123
+ Sort by: self_time
124
+
125
+ %self total self wait child calls name location
126
+ 60.13 1.231 0.741 0.000 0.491 1 DHeap::Benchmarks::Scenarios#repeated_push_pop /home/nick/src/d_heap/lib/d_heap/benchmarks.rb:81
127
+ 16.72 0.206 0.206 0.000 0.000 1000000 DHeap#<<
128
+ 12.85 0.158 0.158 0.000 0.000 1000000 DHeap#pop
129
+ 10.30 0.127 0.127 0.000 0.000 1000000 Array#fetch
130
+
131
+ * recursively called methods
132
+
133
+ Columns are:
134
+
135
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
136
+ total - The time spent in this method and its children.
137
+ self - The time spent in this method.
138
+ wait - The amount of time this method waited for other threads.
139
+ child - The time spent in this method's children.
140
+ calls - The number of times this method was called.
141
+ name - The name of the method.
142
+ location - The location of the method.
143
+
144
+ The interpretation of method names is:
145
+
146
+ * MyObject#test - An instance method "test" of the class "MyObject"
147
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
148
+
149
+ ########################################################################
150
+ # Profile w/ N=1365 (i=1000000)
151
+ # (n.b. RubyProf & tracepoint can change relative performance.
152
+ # A sampling profiler can provide more accurate relative metrics.
153
+ ########################################################################
154
+
155
+ Filling push and resort ---------------------------
156
+ Profiling push and resort ---------------------------
157
+ Measure Mode: wall_time
158
+ Thread ID: 1360
159
+ Fiber ID: 1340
160
+ Total: 41.950612
161
+ Sort by: self_time
162
+
163
+ %self total self wait child calls name location
164
+ 94.10 39.478 39.478 0.000 0.000 1000000 Array#sort!
165
+ 2.11 41.951 0.884 0.000 41.066 1 DHeap::Benchmarks::Scenarios#repeated_push_pop /home/nick/src/d_heap/lib/d_heap/benchmarks.rb:81
166
+ 1.68 40.328 0.707 0.000 39.621 1000000 DHeap::Benchmarks::Sorting#<< /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:35
167
+
168
+ * recursively called methods
169
+
170
+ Columns are:
171
+
172
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
173
+ total - The time spent in this method and its children.
174
+ self - The time spent in this method.
175
+ wait - The amount of time this method waited for other threads.
176
+ child - The time spent in this method's children.
177
+ calls - The number of times this method was called.
178
+ name - The name of the method.
179
+ location - The location of the method.
180
+
181
+ The interpretation of method names is:
182
+
183
+ * MyObject#test - An instance method "test" of the class "MyObject"
184
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
185
+
186
+ Filling bsearch + insert ---------------------------
187
+ Profiling bsearch + insert ---------------------------
188
+ Measure Mode: wall_time
189
+ Thread ID: 1360
190
+ Fiber ID: 1340
191
+ Total: 3.559064
192
+ Sort by: self_time
193
+
194
+ %self total self wait child calls name location
195
+ 37.92 1.349 1.349 0.000 0.000 1000000 Array#bsearch_index
196
+ 22.76 3.559 0.810 0.000 2.749 1 DHeap::Benchmarks::Scenarios#repeated_push_pop /home/nick/src/d_heap/lib/d_heap/benchmarks.rb:81
197
+ 18.47 2.157 0.657 0.000 1.499 1000000 DHeap::Benchmarks::BSearch#<< /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:61
198
+ 9.45 0.462 0.336 0.000 0.125 1000000 DHeap::Benchmarks::BSearch#pop /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:70
199
+ 4.21 0.150 0.150 0.000 0.000 1000000 Array#insert
200
+ 3.67 0.131 0.131 0.000 0.000 1000000 Array#fetch
201
+ 3.53 0.125 0.125 0.000 0.000 1000000 Array#pop
202
+
203
+ * recursively called methods
204
+
205
+ Columns are:
206
+
207
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
208
+ total - The time spent in this method and its children.
209
+ self - The time spent in this method.
210
+ wait - The amount of time this method waited for other threads.
211
+ child - The time spent in this method's children.
212
+ calls - The number of times this method was called.
213
+ name - The name of the method.
214
+ location - The location of the method.
215
+
216
+ The interpretation of method names is:
217
+
218
+ * MyObject#test - An instance method "test" of the class "MyObject"
219
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
220
+
221
+ Filling ruby binary heap ---------------------------
222
+ Profiling ruby binary heap ---------------------------
223
+ Measure Mode: wall_time
224
+ Thread ID: 1360
225
+ Fiber ID: 1340
226
+ Total: 11.581886
227
+ Sort by: self_time
228
+
229
+ %self total self wait child calls name location
230
+ 52.04 6.160 6.027 0.000 0.132 1000000 DHeap::Benchmarks::RbHeap#<< /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:80
231
+ 35.10 4.453 4.065 0.000 0.388 1000000 DHeap::Benchmarks::RbHeap#pop /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:96
232
+ 7.21 11.582 0.835 0.000 10.747 1 DHeap::Benchmarks::Scenarios#repeated_push_pop /home/nick/src/d_heap/lib/d_heap/benchmarks.rb:81
233
+ 1.16 0.134 0.134 0.000 0.000 1000000 Array#fetch
234
+ 1.14 0.132 0.132 0.000 0.000 1000000 Array#push
235
+ 1.14 0.132 0.132 0.000 0.000 1000000 Array#pop
236
+ 1.13 0.131 0.131 0.000 0.000 1000000 Array#first
237
+ 1.08 0.125 0.125 0.000 0.000 1000000 Array#last
238
+
239
+ * recursively called methods
240
+
241
+ Columns are:
242
+
243
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
244
+ total - The time spent in this method and its children.
245
+ self - The time spent in this method.
246
+ wait - The amount of time this method waited for other threads.
247
+ child - The time spent in this method's children.
248
+ calls - The number of times this method was called.
249
+ name - The name of the method.
250
+ location - The location of the method.
251
+
252
+ The interpretation of method names is:
253
+
254
+ * MyObject#test - An instance method "test" of the class "MyObject"
255
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
256
+
257
+ Filling quaternary DHeap ---------------------------
258
+ Profiling quaternary DHeap ---------------------------
259
+ Measure Mode: wall_time
260
+ Thread ID: 1360
261
+ Fiber ID: 1340
262
+ Total: 1.431426
263
+ Sort by: self_time
264
+
265
+ %self total self wait child calls name location
266
+ 50.61 1.431 0.724 0.000 0.707 1 DHeap::Benchmarks::Scenarios#repeated_push_pop /home/nick/src/d_heap/lib/d_heap/benchmarks.rb:81
267
+ 21.61 0.309 0.309 0.000 0.000 1000000 DHeap#<<
268
+ 19.18 0.275 0.275 0.000 0.000 1000000 DHeap#pop
269
+ 8.59 0.123 0.123 0.000 0.000 1000000 Array#fetch
270
+
271
+ * recursively called methods
272
+
273
+ Columns are:
274
+
275
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
276
+ total - The time spent in this method and its children.
277
+ self - The time spent in this method.
278
+ wait - The amount of time this method waited for other threads.
279
+ child - The time spent in this method's children.
280
+ calls - The number of times this method was called.
281
+ name - The name of the method.
282
+ location - The location of the method.
283
+
284
+ The interpretation of method names is:
285
+
286
+ * MyObject#test - An instance method "test" of the class "MyObject"
287
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
288
+
289
+ ########################################################################
290
+ # Profile w/ N=87381 (i=1000000)
291
+ # (n.b. RubyProf & tracepoint can change relative performance.
292
+ # A sampling profiler can provide more accurate relative metrics.
293
+ ########################################################################
294
+
295
+ Filling bsearch + insert ---------------------------
296
+ Profiling bsearch + insert ---------------------------
297
+ Measure Mode: wall_time
298
+ Thread ID: 1360
299
+ Fiber ID: 1340
300
+ Total: 5.894803
301
+ Sort by: self_time
302
+
303
+ %self total self wait child calls name location
304
+ 34.53 2.035 2.035 0.000 0.000 1000000 Array#bsearch_index
305
+ 30.22 1.782 1.782 0.000 0.000 1000000 Array#insert
306
+ 13.74 5.895 0.810 0.000 5.085 1 DHeap::Benchmarks::Scenarios#repeated_push_pop /home/nick/src/d_heap/lib/d_heap/benchmarks.rb:81
307
+ 11.52 4.496 0.679 0.000 3.817 1000000 DHeap::Benchmarks::BSearch#<< /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:61
308
+ 5.70 0.459 0.336 0.000 0.124 1000000 DHeap::Benchmarks::BSearch#pop /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:70
309
+ 2.20 0.130 0.130 0.000 0.000 1000000 Array#fetch
310
+ 2.10 0.124 0.124 0.000 0.000 1000000 Array#pop
311
+
312
+ * recursively called methods
313
+
314
+ Columns are:
315
+
316
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
317
+ total - The time spent in this method and its children.
318
+ self - The time spent in this method.
319
+ wait - The amount of time this method waited for other threads.
320
+ child - The time spent in this method's children.
321
+ calls - The number of times this method was called.
322
+ name - The name of the method.
323
+ location - The location of the method.
324
+
325
+ The interpretation of method names is:
326
+
327
+ * MyObject#test - An instance method "test" of the class "MyObject"
328
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
329
+
330
+ Filling ruby binary heap ---------------------------
331
+ Profiling ruby binary heap ---------------------------
332
+ Measure Mode: wall_time
333
+ Thread ID: 1360
334
+ Fiber ID: 1340
335
+ Total: 16.514635
336
+ Sort by: self_time
337
+
338
+ %self total self wait child calls name location
339
+ 45.67 7.926 7.542 0.000 0.384 1000000 DHeap::Benchmarks::RbHeap#pop /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:96
340
+ 45.43 7.630 7.502 0.000 0.128 1000000 DHeap::Benchmarks::RbHeap#<< /home/nick/src/d_heap/lib/d_heap/benchmarks/implementations.rb:80
341
+ 5.00 16.515 0.826 0.000 15.688 1 DHeap::Benchmarks::Scenarios#repeated_push_pop /home/nick/src/d_heap/lib/d_heap/benchmarks.rb:81
342
+
343
+ * recursively called methods
344
+
345
+ Columns are:
346
+
347
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
348
+ total - The time spent in this method and its children.
349
+ self - The time spent in this method.
350
+ wait - The amount of time this method waited for other threads.
351
+ child - The time spent in this method's children.
352
+ calls - The number of times this method was called.
353
+ name - The name of the method.
354
+ location - The location of the method.
355
+
356
+ The interpretation of method names is:
357
+
358
+ * MyObject#test - An instance method "test" of the class "MyObject"
359
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
360
+
361
+ Filling quaternary DHeap ---------------------------
362
+ Profiling quaternary DHeap ---------------------------
363
+ Measure Mode: wall_time
364
+ Thread ID: 1360
365
+ Fiber ID: 1340
366
+ Total: 1.622729
367
+ Sort by: self_time
368
+
369
+ %self total self wait child calls name location
370
+ 44.66 1.623 0.725 0.000 0.898 1 DHeap::Benchmarks::Scenarios#repeated_push_pop /home/nick/src/d_heap/lib/d_heap/benchmarks.rb:81
371
+ 27.42 0.445 0.445 0.000 0.000 1000000 DHeap#pop
372
+ 20.41 0.331 0.331 0.000 0.000 1000000 DHeap#<<
373
+ 7.51 0.122 0.122 0.000 0.000 1000000 Array#fetch
374
+
375
+ * recursively called methods
376
+
377
+ Columns are:
378
+
379
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
380
+ total - The time spent in this method and its children.
381
+ self - The time spent in this method.
382
+ wait - The amount of time this method waited for other threads.
383
+ child - The time spent in this method's children.
384
+ calls - The number of times this method was called.
385
+ name - The name of the method.
386
+ location - The location of the method.
387
+
388
+ The interpretation of method names is:
389
+
390
+ * MyObject#test - An instance method "test" of the class "MyObject"
391
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
392
+
@@ -1,377 +1,955 @@
1
- #include "d_heap.h"
2
-
3
- ID id_ivar_a;
4
- ID id_ivar_d;
5
-
6
- #define DHEAP_GET_A(self) rb_ivar_get(self, id_ivar_a)
7
- #define DHEAP_GET_D(self) rb_ivar_get(self, id_ivar_d)
8
-
9
- #define DHEAP_SIZE(ary) (RARRAY_LEN(ary) / 2)
10
- #define DHEAP_LAST_IDX(ary) (DHEAP_SIZE(ary) - 1)
11
- #define DHEAP_VALUE(ary, idx) rb_ary_entry(ary, idx * 2 + 1)
12
- #define DHEAP_SCORE(ary, idx) rb_ary_entry(ary, idx * 2)
13
- #define DHEAP_ASSIGN(ary, idx, scr, val) \
14
- rb_ary_store(ary, idx * 2, scr); \
15
- rb_ary_store(ary, idx * 2 + 1, val);
16
- #define DHEAP_APPEND(ary, scr, val) \
17
- rb_ary_push(ary, scr); \
18
- rb_ary_push(ary, val);
19
- #define DHEAP_DROP_LAST(ary) ( \
20
- rb_ary_pop(ary), \
21
- rb_ary_pop(ary) \
22
- ) // score, value
23
- #define IDX_PARENT(idx) ((idx - 1) / d)
24
- #define IDX_CHILD0(idx) ((idx * d) + 1)
25
-
26
- #define DHEAP_Check_d_size(d) \
27
- if (d < 2) { \
28
- rb_raise(rb_eArgError, "DHeap d=%d is too small", d); \
29
- } \
30
- if (d > DHEAP_MAX_D) { \
31
- rb_raise(rb_eArgError, "DHeap d=%d is too large", d); \
1
+ #include <float.h>
2
+ #include <math.h>
3
+ #include "ruby.h"
4
+
5
+ /********************************************************************
6
+ *
7
+ * Type definitions
8
+ *
9
+ ********************************************************************/
10
+
11
+ typedef struct dheap_struct dheap_t;
12
+ typedef struct dheap_entry ENTRY;
13
+
14
+ // TODO: convert SCORE to a union, and use an ENTRY flag for its type
15
+ typedef long double SCORE;
16
+
17
+ #if LDBL_MANT_DIG < SIZEOF_UNSIGNED_LONG_LONG * 8
18
+ #error 'unsigned long long' must fit into 'long double' mantissa
19
+ #endif
20
+
21
+ // TODO: test this code on a 32 bit system (it MUST have some bugs!)
22
+ #if SIZEOF_UNSIGNED_LONG_LONG * 8 != 64
23
+ #error 'unsigned long long' must be 64bits
24
+ #endif
25
+
26
+ /********************************************************************
27
+ *
28
+ * Struct definitions
29
+ *
30
+ ********************************************************************/
31
+
32
+ struct dheap_struct {
33
+ int d;
34
+ long size;
35
+ long capa;
36
+ ENTRY *entries;
37
+ };
38
+
39
+ struct dheap_entry {
40
+ SCORE score;
41
+ VALUE value;
42
+ };
43
+
44
+ /********************************************************************
45
+ *
46
+ * Constant definitions
47
+ *
48
+ ********************************************************************/
49
+
50
+ #define DHEAP_DEFAULT_D 4
51
+ #define DHEAP_MAX_D INT_MAX
52
+
53
+ // sizeof(ENTRY) => 32 bytes
54
+ // one kilobyte = 32 * 32 bytes
55
+ #define DHEAP_DEFAULT_CAPA 32
56
+ #define DHEAP_MAX_CAPA (LONG_MAX / (int)sizeof(ENTRY))
57
+ #define DHEAP_CAPA_INCR_MAX (10 * 1024 * 1024 / (int)sizeof(ENTRY))
58
+
59
+ static ID id_cmp; // <=>
60
+ static ID id_abs; // abs
61
+ static ID id_lshift; // <<
62
+ static ID id_unary_minus; // -@
63
+ static const ENTRY EmptyDheapEntry; // 0 value for safety overwrite after pop
64
+ static const rb_data_type_t dheap_data_type;
65
+
66
+ /********************************************************************
67
+ *
68
+ * SCORE: casting to and from VALUE
69
+ * adapted from similar methods in ruby's object.c
70
+ *
71
+ ********************************************************************/
72
+
73
+ // ruby doesn't have a LDBL2NUM. :(
74
+ // So this only accomplishes a subset of what that ought to do.
75
+ static inline VALUE
76
+ SCORE2NUM(SCORE s)
77
+ {
78
+ if (floorl((long double) s) == s) {
79
+ if (s < 0) {
80
+ unsigned long long ull = (unsigned long long)(-s);
81
+ return rb_funcall(ULL2NUM(ull), id_unary_minus, 0, Qundef);
82
+ }
83
+ return ULL2NUM((unsigned long long)(s));
32
84
  }
85
+ return rb_float_new((double)(s));
86
+ }
33
87
 
34
- #define DHEAP_Check_Sift_Idx(sift_index, last_index) \
35
- if (sift_index < 0) { \
36
- rb_raise(rb_eIndexError, "sift_index %ld too small", sift_index); \
37
- } \
38
- else if (last_index < sift_index) { \
39
- rb_raise(rb_eIndexError, "sift_index %ld too large", sift_index); \
88
+ static inline SCORE
89
+ FIX2SCORE(VALUE x)
90
+ {
91
+ return (long double)FIX2LONG(x);
92
+ }
93
+
94
+ // We could translate a much wider range of values to long double by
95
+ // implementing a new `rb_big2ldbl(x)` function. But requires reaching into
96
+ // T_BIGNUM internals.
97
+ static inline long double
98
+ BIG2SCORE(VALUE x)
99
+ {
100
+ if (RBIGNUM_POSITIVE_P(x)) {
101
+ unsigned long long ull = rb_big2ull(x);
102
+ return (long double)ull;
103
+ } else {
104
+ unsigned long long ull;
105
+ long double ldbl;
106
+ x = rb_funcall(x, id_abs, 0, Qundef);
107
+ ull = rb_big2ull(x);
108
+ ldbl = (long double) ull;
109
+ return -ldbl;
40
110
  }
111
+ }
41
112
 
42
- #define DHEAP_Check_Sift_Args(heap_array, d, sift_index) \
43
- DHEAP_Check_d_size(d); \
44
- Check_Type(heap_array, T_ARRAY); \
45
- long last_index = DHEAP_LAST_IDX(heap_array); \
46
- DHEAP_Check_Sift_Idx(sift_index, last_index); \
47
- \
48
- VALUE sift_value = DHEAP_VALUE(heap_array, sift_index); \
49
- VALUE sift_score = DHEAP_SCORE(heap_array, sift_index);
113
+ static inline SCORE
114
+ INT2SCORE(VALUE x)
115
+ {
116
+ return (FIXNUM_P(x) ? FIX2SCORE(x) : BIG2SCORE(x));
117
+ }
50
118
 
51
- VALUE
52
- dheap_ary_sift_up(VALUE heap_array, int d, long sift_index) {
53
- DHEAP_Check_Sift_Args(heap_array, d, sift_index);
54
- // sift it up to where it belongs
55
- for (long parent_index; 0 < sift_index; sift_index = parent_index) {
56
- debug(rb_sprintf("sift up(%"PRIsVALUE", %d, %ld)", heap_array, d, sift_index));
57
- parent_index = IDX_PARENT(sift_index);
58
- VALUE parent_score = DHEAP_SCORE(heap_array, parent_index);
119
+ static inline SCORE
120
+ NUM2SCORE(VALUE x)
121
+ {
122
+ return (FIXNUM_P(x) ? FIX2SCORE(x) :
123
+ RB_TYPE_P(x, T_BIGNUM) ? BIG2SCORE(x) :
124
+ (Check_Type(x, T_FLOAT), (long double)RFLOAT_VALUE(x)));
125
+ }
59
126
 
60
- // parent is smaller: heap is restored
61
- if (CMP_LTE(parent_score, sift_score)) break;
127
+ static inline long double
128
+ RAT2SCORE(VALUE x)
129
+ {
130
+ VALUE num = rb_rational_num(x);
131
+ VALUE den = rb_rational_den(x);
132
+ return NUM2SCORE(num) / NUM2SCORE(den);
133
+ }
62
134
 
63
- // parent is larger: swap and continue sifting up
64
- VALUE parent_value = DHEAP_VALUE(heap_array, parent_index);
65
- DHEAP_ASSIGN(heap_array, sift_index, parent_score, parent_value);
66
- DHEAP_ASSIGN(heap_array, parent_index, sift_score, sift_value);
135
+ /*
136
+ * Convert both T_FIXNUM and T_FLOAT (and sometimes T_BIGNUM, T_RATIONAL,
137
+ * String, etc) to SCORE
138
+ * * with no loss of precision (where possible for Integer and Float),
139
+ * * raises an exception if
140
+ * * a positive integer is too large for unsigned long long (should be 64bit)
141
+ * * a negative integer is too small for signed long long (should be 64bit)
142
+ * * reduced to long double (should be 80 or 128 bit) if it is Rational
143
+ * * reduced to double precision if the value is convertable by Float(x)
144
+ */
145
+ static inline SCORE
146
+ VAL2SCORE(VALUE score)
147
+ {
148
+ // assert that long double can hold 'unsigned long long':
149
+ // static_assert(sizeof(unsigned long long) * 8 <= LDBL_MANT_DIG);
150
+ // assert that long double can hold T_FLOAT
151
+ // static_assert(sizeof(double) <= sizeof(long double));
152
+ switch (TYPE(score)) {
153
+ case T_FIXNUM:
154
+ return FIX2SCORE(score);
155
+ case T_BIGNUM:
156
+ return BIG2SCORE(score);
157
+ case T_RATIONAL:
158
+ return RAT2SCORE(score);
159
+ default:
160
+ return (long double)(NUM2DBL(rb_Float(score)));
67
161
  }
68
- debug(rb_sprintf("sifted (%"PRIsVALUE", %d, %ld)", heap_array, d, sift_index));
69
- return LONG2NUM(sift_index);
70
162
  }
71
163
 
72
- VALUE
73
- dheap_ary_sift_down(VALUE heap_array, int d, long sift_index) {
74
- DHEAP_Check_Sift_Args(heap_array, d, sift_index);
75
-
76
- // iteratively sift it down to where it belongs
77
- for (long child_index; sift_index < last_index; sift_index = child_index) {
78
- debug(rb_sprintf("sift dn(%"PRIsVALUE", %d, %ld)", heap_array, d, sift_index));
79
- // find first child index, and break if we've reached the last layer
80
- long child_idx0 = child_index = IDX_CHILD0(sift_index);
81
- if (last_index < child_idx0) break;
82
-
83
- // find the min child (and its child_index)
84
- // requires "d" comparisons to find min child and compare to sift_score
85
- VALUE child_score = DHEAP_SCORE(heap_array, child_idx0);
86
- child_index = child_idx0;
87
- for (int i = 1; i < d; ++i) {
88
- long sibling_index = child_idx0 + i;
89
- if (last_index < sibling_index) break;
90
-
91
- VALUE sibling_score = DHEAP_SCORE(heap_array, sibling_index);
92
-
93
- if (CMP_LT(sibling_score, child_score)) {
94
- child_score = sibling_score;
95
- child_index = sibling_index;
96
- }
97
- }
164
+ /********************************************************************
165
+ *
166
+ * DHeap ENTRY accessors
167
+ *
168
+ ********************************************************************/
169
+
170
+ #define DHEAP_SCORE(heap, idx) ((heap)->entries[idx].score)
171
+ #define DHEAP_VALUE(heap, idx) ((heap)->entries[idx].value)
172
+
173
+ static inline VALUE
174
+ DHEAP_ENTRY_ARY(dheap_t *heap, long idx)
175
+ {
176
+ if (idx < 0 || heap->size <= idx) { return Qnil; }
177
+ return rb_ary_new_from_args(2,
178
+ DHEAP_VALUE(heap, 0),
179
+ SCORE2NUM(DHEAP_SCORE(heap, 0)));
180
+ }
98
181
 
99
- // child is larger: heap is restored
100
- if (CMP_LTE(sift_score, child_score)) break;
182
+ /********************************************************************
183
+ *
184
+ * DHeap index math
185
+ *
186
+ ********************************************************************/
187
+
188
+ #define DHEAP_IDX_LAST(heap) ((heap)->size - 1)
189
+ #define DHEAP_IDX_PARENT(heap, idx) (((idx) - 1) / (heap)->d)
190
+ #define DHEAP_IDX_CHILD_0(heap, idx) (((idx) * (heap)->d) + 1)
191
+ #define DHEAP_IDX_CHILD_D(heap, idx) (((idx) * (heap)->d) + (heap)->d)
192
+
193
+ #ifdef __D_HEAP_DEBUG
194
+ #define ASSERT_DHEAP_IDX_OK(heap, index) do { \
195
+ if (index < 0) { \
196
+ rb_raise(rb_eIndexError, "DHeap index %ld too small", index); \
197
+ } \
198
+ else if (DHEAP_IDX_LAST(heap) < index) { \
199
+ rb_raise(rb_eIndexError, "DHeap index %ld too large", index); \
200
+ } \
201
+ } while (0)
202
+ #else
203
+ #define ASSERT_DHEAP_IDX_OK(heap, index)
204
+ #endif
205
+
206
+ /********************************************************************
207
+ *
208
+ * rb_data_type_t definitions
209
+ *
210
+ ********************************************************************/
101
211
 
102
- // child is smaller: swap and continue sifting down
103
- VALUE child_value = DHEAP_VALUE(heap_array, child_index);
104
- DHEAP_ASSIGN(heap_array, sift_index, child_score, child_value);
105
- DHEAP_ASSIGN(heap_array, child_index, sift_score, sift_value);
212
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
213
+ static void
214
+ dheap_compact(void *ptr)
215
+ {
216
+ dheap_t *heap = ptr;
217
+ for (long i = 0; i < heap->size; ++i) {
218
+ if (DHEAP_VALUE(heap, i))
219
+ rb_gc_location(DHEAP_VALUE(heap, i));
106
220
  }
107
- debug(rb_sprintf("sifted (%"PRIsVALUE", %d, %ld)", heap_array, d, sift_index));
108
- return LONG2NUM(sift_index);
109
221
  }
222
+ #else
223
+ #define rb_gc_mark_movable(x) rb_gc_mark(x)
224
+ #endif
110
225
 
111
- #define DHEAP_Load_Sift_Vals(heap_array, dval, idxval) \
112
- Check_Type(dval, T_FIXNUM); \
113
- int dint = FIX2INT(dval); \
114
- long idx = NUM2LONG(idxval);
226
+ static void
227
+ dheap_mark(void *ptr)
228
+ {
229
+ dheap_t *heap = ptr;
230
+ for (long i = 0; i < heap->size; ++i) {
231
+ if (DHEAP_VALUE(heap, i))
232
+ rb_gc_mark_movable(DHEAP_VALUE(heap,i));
233
+ }
234
+ }
115
235
 
116
- /*
117
- * Treats a +heap_array+ as a +d+-ary heap and sifts up from +sift_index+ to
118
- * restore the heap property for all nodes between it and the root of the tree.
236
+ static void
237
+ dheap_free(void *ptr)
238
+ {
239
+ dheap_t *heap = ptr;
240
+ heap->size = 0;
241
+ if (heap->entries) {
242
+ xfree(heap->entries);
243
+ heap->entries = NULL;
244
+ }
245
+ heap->capa = 0;
246
+ xfree(ptr);
247
+ }
248
+
249
+ static size_t
250
+ dheap_memsize(const void *ptr)
251
+ {
252
+ const dheap_t *heap = ptr;
253
+ size_t size = 0;
254
+ size += sizeof(*heap);
255
+ size += sizeof(ENTRY) * heap->capa;
256
+ return size;
257
+ }
258
+
259
+
260
+ static const rb_data_type_t dheap_data_type = {
261
+ "DHeap",
262
+ {
263
+ (void (*)(void*))dheap_mark,
264
+ (void (*)(void*))dheap_free,
265
+ (size_t (*)(const void *))dheap_memsize,
266
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
267
+ (void (*)(void*))dheap_compact, {0}
268
+ #else
269
+ {0}
270
+ #endif
271
+ },
272
+ 0, 0,
273
+ RUBY_TYPED_FREE_IMMEDIATELY,
274
+ };
275
+
276
+ /********************************************************************
119
277
  *
120
- * The array is interpreted as holding two entries for each node, a score and a
121
- * value. The scores will held in every even-numbered array index and the
122
- * values in every odd numbered index. The array is flat, not an array of
123
- * length=2 arrays.
278
+ * DHeap comparisons
279
+ * TODO: bring back comparisons for score types other than `long double`.
124
280
  *
125
- * Time complexity: <b>O(log n / log d)</b> <i>(worst-case)</i>
281
+ ********************************************************************/
282
+
283
+ #define CMP_LT(a, b) ((a) < (b))
284
+ #define CMP_LTE(a, b) ((a) <= (b))
285
+
286
+ /* #ifdef ORIG_SCORE_CMP_CODE */
287
+
288
+ /* #define CMP_LT(a, b) (optimized_cmp(a, b) < 0) */
289
+ /* #define CMP_LTE(a, b) (optimized_cmp(a, b) <= 0) */
290
+
291
+ /* // from internal/compar.h */
292
+ /* #define STRING_P(s) (RB_TYPE_P((s), T_STRING) && CLASS_OF(s) == rb_cString) */
293
+ /*
294
+ * short-circuit evaluation for a few basic types.
126
295
  *
127
- * @param heap_array [Array] the array which is treated a heap.
128
- * @param d [Integer] the maximum number of children per parent
129
- * @param sift_index [Integer] the index to start sifting from
130
- * @return [Integer] the new index for the object that starts at +sift_index+.
296
+ * Only Integer, Float, and String are optimized,
297
+ * and only when both arguments are the same type.
131
298
  */
299
+ /* static inline int */
300
+ /* optimized_cmp(SCORE a, SCORE b) { */
301
+ /* if (a == b) // Fixnum equality and object equality */
302
+ /* return 0; */
303
+ /* if (FIXNUM_P(a) && FIXNUM_P(b)) */
304
+ /* return (FIX2LONG(a) < FIX2LONG(b)) ? -1 : 1; */
305
+ /* if (RB_FLOAT_TYPE_P(a) && RB_FLOAT_TYPE_P(b)) */
306
+ /* { */
307
+ /* double x, y; */
308
+ /* x = RFLOAT_VALUE(a); */
309
+ /* y = RFLOAT_VALUE(b); */
310
+ /* if (isnan(x) || isnan(y)) rb_cmperr(a, b); // raise ArgumentError */
311
+ /* return (x < y) ? -1 : ((x == y) ? 0 : 1); */
312
+ /* } */
313
+ /* if (RB_TYPE_P(a, T_BIGNUM) && RB_TYPE_P(b, T_BIGNUM)) */
314
+ /* return FIX2INT(rb_big_cmp(a, b)); */
315
+ /* if (STRING_P(a) && STRING_P(b)) */
316
+ /* return rb_str_cmp(a, b); */
317
+
318
+ /* // give up on an optimized version and just call (a <=> b) */
319
+ /* return rb_cmpint(rb_funcallv(a, id_cmp, 1, &b), a, b); */
320
+ /* } */
321
+
322
+ /* #endif */
323
+
324
+ /********************************************************************
325
+ *
326
+ * DHeap allocation and initialization and resizing
327
+ *
328
+ ********************************************************************/
329
+
132
330
  static VALUE
133
- dheap_sift_up_s(VALUE unused, VALUE heap_array, VALUE d, VALUE sift_index) {
134
- DHEAP_Load_Sift_Vals(heap_array, d, sift_index);
135
- return dheap_ary_sift_up(heap_array, dint, idx);
331
+ dheap_s_alloc(VALUE klass)
332
+ {
333
+ VALUE obj;
334
+ dheap_t *heap;
335
+
336
+ // TypedData_Make_Struct uses a non-std "statement expression"
337
+ #pragma GCC diagnostic push
338
+ #pragma GCC diagnostic ignored "-Wpedantic"
339
+ obj = TypedData_Make_Struct(klass, dheap_t, &dheap_data_type, heap);
340
+ #pragma GCC diagnostic pop
341
+ heap->d = DHEAP_DEFAULT_D;
342
+
343
+ heap->size = 0;
344
+ heap->capa = 0;
345
+ heap->entries = NULL;
346
+
347
+ return obj;
136
348
  }
137
349
 
138
- /*
139
- * Treats +heap_array+ as a +d+-ary heap and sifts down from +sift_index+ to
140
- * restore the heap property. If all _d_ subtrees below +sift_index+ are already
141
- * heaps, this method ensures the entire subtree rooted at +sift_index+ will be
142
- * a heap.
143
- *
144
- * The array is interpreted as holding two entries for each node, a score and a
145
- * value. The scores will held in every even-numbered array index and the
146
- * values in every odd numbered index. The array is flat, not an array of
147
- * length=2 arrays.
350
+ static inline dheap_t *
351
+ get_dheap_struct(VALUE self)
352
+ {
353
+ dheap_t *heap;
354
+ TypedData_Get_Struct(self, dheap_t, &dheap_data_type, heap);
355
+ return heap;
356
+ }
357
+
358
+ void
359
+ dheap_set_capa(dheap_t *heap, long new_capa)
360
+ {
361
+ // Do nothing if we already have the capacity or are resizing too small
362
+ if (new_capa <= heap->capa || new_capa <= heap->size) return;
363
+
364
+ // allocate
365
+ if (heap->entries) {
366
+ RB_REALLOC_N(heap->entries, ENTRY, new_capa);
367
+ } else {
368
+ heap->entries = RB_ZALLOC_N(ENTRY, new_capa);
369
+ }
370
+ heap->capa = new_capa;
371
+ }
372
+
373
+ static void
374
+ dheap_ensure_room_for_push(dheap_t *heap, long incr_by)
375
+ {
376
+ long new_size = heap->size + incr_by;
377
+
378
+ // check for overflow of new_size
379
+ if (DHEAP_MAX_CAPA - incr_by < heap->size)
380
+ rb_raise(rb_eIndexError, "index %ld too big", new_size);
381
+
382
+ // if it existing capacity is too small
383
+ if (heap->capa < new_size) {
384
+ // double it...
385
+ long new_capa = new_size * 2;
386
+ if (DHEAP_CAPA_INCR_MAX < new_size)
387
+ new_size = new_size + DHEAP_CAPA_INCR_MAX;
388
+ // check for overflow of new_capa
389
+ if (DHEAP_MAX_CAPA / 2 < new_size) new_capa = DHEAP_MAX_CAPA;
390
+ // cap max incr_by
391
+ if (heap->capa + DHEAP_CAPA_INCR_MAX < new_capa)
392
+ new_capa = heap->capa + DHEAP_CAPA_INCR_MAX;
393
+
394
+ dheap_set_capa(heap, new_capa);
395
+ }
396
+ }
397
+
398
+ static inline int
399
+ dheap_value_to_int_d(VALUE num)
400
+ {
401
+ int d = NUM2INT(num);
402
+ if (d < 2) {
403
+ rb_raise(rb_eArgError, "DHeap d=%u is too small", d);
404
+ }
405
+ if (d > DHEAP_MAX_D) {
406
+ rb_raise(rb_eArgError, "DHeap d=%u is too large", d);
407
+ }
408
+ return d;
409
+ }
410
+
411
+ static inline long
412
+ dheap_value_to_long_capa(VALUE num)
413
+ {
414
+ long capa = NUM2LONG(num);
415
+ if (capa < 1) {
416
+ rb_raise(rb_eArgError, "DHeap capa=%lu must be positive", capa);
417
+ }
418
+ return capa;
419
+ }
420
+
421
+ static VALUE
422
+ dheap_initialize(VALUE self, VALUE d, VALUE capa) {
423
+ dheap_t *heap = get_dheap_struct(self);
424
+
425
+ if(heap->entries || heap->size || heap->capa)
426
+ rb_raise(rb_eScriptError, "DHeap already initialized.");
427
+
428
+ heap->d = dheap_value_to_int_d(d);
429
+ dheap_set_capa(heap, dheap_value_to_long_capa(capa));
430
+
431
+ return self;
432
+ }
433
+
434
+ /* @!visibility private */
435
+ static VALUE
436
+ dheap_initialize_copy(VALUE copy, VALUE orig)
437
+ {
438
+ dheap_t *heap_copy;
439
+ dheap_t *heap_orig = get_dheap_struct(orig);
440
+
441
+ rb_check_frozen(copy);
442
+ TypedData_Get_Struct(copy, dheap_t, &dheap_data_type, heap_copy);
443
+
444
+ heap_copy->d = heap_orig->d;
445
+
446
+ dheap_set_capa(heap_copy, heap_orig->capa);
447
+ heap_copy->size = heap_orig->size;
448
+ if (heap_copy->size)
449
+ MEMCPY(heap_copy->entries, heap_orig->entries, ENTRY, heap_orig->size);
450
+
451
+ return copy;
452
+ }
453
+
454
+ /********************************************************************
148
455
  *
149
- * Time complexity: <b>O(d log n / log d)</b> <i>(worst-case)</i>
456
+ * DHeap sift up/down
150
457
  *
151
- * @param heap_array [Array] the array which is treated a heap.
152
- * @param d [Integer] the maximum number of children per parent
153
- * @param sift_index [Integer] the index to start sifting down from
154
- * @return [Integer] the new index for the object that starts at +sift_index+.
155
- */
156
- static VALUE
157
- dheap_sift_down_s(VALUE unused, VALUE heap_array, VALUE d, VALUE sift_index) {
158
- DHEAP_Load_Sift_Vals(heap_array, d, sift_index);
159
- return dheap_ary_sift_down(heap_array, dint, idx);
458
+ ********************************************************************/
459
+
460
+ VALUE
461
+ dheap_sift_up(dheap_t *heap, long index) {
462
+ ENTRY entry = heap->entries[index];
463
+
464
+ ASSERT_DHEAP_IDX_OK(heap, index);
465
+
466
+ // sift it up to where it belongs
467
+ for (long parent_index; 0 < index; index = parent_index) {
468
+ /* debug(rb_sprintf("sift up(%"PRIsVALUE", %d, %ld)", heap->values, heap->d, index)); */
469
+ parent_index = DHEAP_IDX_PARENT(heap, index);
470
+
471
+ // parent is smaller: heap is restored
472
+ if (CMP_LTE(DHEAP_SCORE(heap, parent_index), entry.score)) break;
473
+
474
+ // parent is larger: swap and continue sifting up
475
+ heap->entries[index] = heap->entries[parent_index];
476
+ }
477
+ heap->entries[index] = entry;
478
+ /* debug(rb_sprintf("sifted (%"PRIsVALUE", %d, %ld)", heap->values, heap->d, index)); */
479
+ return LONG2NUM(index);
160
480
  }
161
481
 
162
482
  /*
163
- * @overload initialize(d = DHeap::DEFAULT_D)
164
- * Initialize a _d_-ary min-heap.
165
- *
166
- * @param d [Integer] maximum number of children per parent
483
+ * this is a tiny bit more complicated than the binary heap version
167
484
  */
168
- static VALUE
169
- dheap_initialize(int argc, VALUE *argv, VALUE self) {
170
- rb_check_arity(argc, 0, 1);
171
- int d = DHEAP_DEFAULT_D;
172
- if (argc) {
173
- d = NUM2INT(argv[0]);
485
+ static inline long
486
+ dheap_find_min_child(dheap_t *heap, long parent, long last_index) {
487
+ long min_child = DHEAP_IDX_CHILD_0(heap, parent);
488
+ long last_sib = DHEAP_IDX_CHILD_D(heap, parent);
489
+ if (last_index < last_sib) last_sib = last_index;
490
+
491
+ for (long sibidx = min_child + 1; sibidx <= last_sib; ++sibidx) {
492
+ if (CMP_LT(DHEAP_SCORE(heap, sibidx),
493
+ DHEAP_SCORE(heap, min_child))) {
494
+ min_child = sibidx;
495
+ }
174
496
  }
175
- DHEAP_Check_d_size(d);
176
- rb_ivar_set(self, id_ivar_d, INT2FIX(d));
177
- rb_ivar_set(self, id_ivar_a, rb_ary_new());
178
- return self;
497
+ return min_child;
179
498
  }
180
499
 
500
+ void
501
+ dheap_sift_down(dheap_t *heap, long index, long last_index) {
502
+ if (last_index < 1 || DHEAP_IDX_PARENT(heap, last_index) < index) {
503
+ // short-circuit: no chance for a child
504
+ return;
505
+
506
+ } else {
507
+ ENTRY entry = heap->entries[index];
508
+
509
+ long last_parent = DHEAP_IDX_PARENT(heap, last_index);
510
+
511
+ ASSERT_DHEAP_IDX_OK(heap, index);
512
+
513
+ // iteratively sift it down to where it belongs
514
+ while (index <= last_parent) {
515
+ // find min child
516
+ long min_child = dheap_find_min_child(heap, index, last_index);
517
+
518
+ // child is larger: heap is restored
519
+ if (CMP_LTE(entry.score, DHEAP_SCORE(heap, min_child))) break;
520
+
521
+ // child is smaller: swap and continue sifting down
522
+ heap->entries[index] = heap->entries[min_child];
523
+ index = min_child;
524
+ }
525
+ heap->entries[index] = entry;
526
+ // debug(rb_sprintf("sifted (%"PRIsVALUE", %d, %ld)", heap->values, heap->d, index));
527
+ }
528
+ }
529
+
530
+ /********************************************************************
531
+ *
532
+ * DHeap attributes
533
+ *
534
+ ********************************************************************/
535
+
181
536
  /*
182
537
  * @return [Integer] the number of elements in the heap
183
538
  */
184
- static VALUE dheap_size(VALUE self) {
185
- VALUE ary = DHEAP_GET_A(self);
186
- long size = DHEAP_SIZE(ary);
187
- return LONG2NUM(size);
539
+ static VALUE
540
+ dheap_size(VALUE self)
541
+ {
542
+ dheap_t *heap = get_dheap_struct(self);
543
+ return LONG2NUM(heap->size);
188
544
  }
189
545
 
190
546
  /*
191
- * @return [Boolean] is the heap empty?
547
+ * @return [Boolean] if the heap is empty
192
548
  */
193
- static VALUE dheap_empty_p(VALUE self) {
194
- VALUE ary = DHEAP_GET_A(self);
195
- long size = DHEAP_SIZE(ary);
196
- return size == 0 ? Qtrue : Qfalse;
549
+ static VALUE
550
+ dheap_empty_p(VALUE self)
551
+ {
552
+ dheap_t *heap = get_dheap_struct(self);
553
+ return heap->size ? Qfalse : Qtrue;
197
554
  }
198
555
 
199
556
  /*
200
557
  * @return [Integer] the maximum number of children per parent
201
558
  */
202
- static VALUE dheap_attr_d(VALUE self) { return DHEAP_GET_D(self); }
559
+ static VALUE
560
+ dheap_attr_d(VALUE self)
561
+ {
562
+ dheap_t *heap = get_dheap_struct(self);
563
+ return INT2FIX(heap->d);
564
+ }
565
+
566
+ /********************************************************************
567
+ *
568
+ * DHeap push
569
+ *
570
+ ********************************************************************/
571
+
572
+ static inline void
573
+ dheap_push_entry(dheap_t *heap, ENTRY *entry) {
574
+ dheap_ensure_room_for_push(heap, 1);
575
+ heap->entries[heap->size] = *entry;
576
+ ++heap->size;
577
+ dheap_sift_up(heap, heap->size - 1);
578
+ }
203
579
 
204
580
  /*
205
- * Freezes the heap as well as its underlying array, but does <i>not</i>
206
- * deep-freeze the elements in the heap.
581
+ * Inserts a value into the heap, using a score to determine sort-order.
582
+ *
583
+ * Score comes first, as an analogy with the +Array#insert+ index.
584
+ *
585
+ * Time complexity: <b>O(log n / log d)</b> <i>(worst-case)</i>
586
+ *
587
+ * @param score [Integer,Float,#to_f] a score to compare against other scores.
588
+ * @param value [Object] an object that is associated with the score.
207
589
  *
208
590
  * @return [self]
209
591
  */
210
592
  static VALUE
211
- dheap_freeze(VALUE self) {
212
- VALUE ary = DHEAP_GET_A(self);
213
- ID id_freeze = rb_intern("freeze");
214
- rb_funcall(ary, id_freeze, 0);
215
- return rb_call_super(0, NULL);
216
- }
593
+ dheap_insert(VALUE self, VALUE score, VALUE value) {
594
+ ENTRY entry;
595
+ dheap_t *heap = get_dheap_struct(self);
596
+ rb_check_frozen(self);
217
597
 
218
- static VALUE
219
- dheap_ary_push(VALUE ary, int d, VALUE val, VALUE scr)
220
- {
221
- DHEAP_APPEND(ary, scr, val);
222
- long last_index = DHEAP_LAST_IDX(ary);
223
- return dheap_ary_sift_up(ary, d, last_index);
598
+ entry.score = VAL2SCORE(score);
599
+ entry.value = value;
600
+ dheap_push_entry(heap, &entry);
601
+
602
+ return self;
224
603
  }
225
604
 
226
605
  /*
227
- * @overload push(score, value = score)
606
+ * @overload push(value, score = value)
228
607
  *
229
608
  * Push a value onto heap, using a score to determine sort-order.
230
609
  *
231
- * Ideally, the score should be a frozen value that can be efficiently compared
232
- * to other scores, e.g. an Integer or Float or (maybe) a String
610
+ * Value comes first because the separate score is optional, and because it feels
611
+ * like a more natural variation on +Array#push+ or +Queue#enq+. If a score
612
+ * isn't provided, the value must be an Integer or can be cast with
613
+ * +Float(value)+.
233
614
  *
234
615
  * Time complexity: <b>O(log n / log d)</b> <i>(worst-case)</i>
235
616
  *
236
- * @param score [#<=>] a value that can be compared to other scores.
237
617
  * @param value [Object] an object that is associated with the score.
618
+ * @param score [Integer,Float,#to_f] a score to compare against other scores.
238
619
  *
239
- * @return [Integer] the index of the value's final position.
620
+ * @return [self]
240
621
  */
241
622
  static VALUE
242
623
  dheap_push(int argc, VALUE *argv, VALUE self) {
243
- rb_check_arity(argc, 1, 2);
244
- VALUE scr = argv[0];
245
- VALUE val = argc < 2 ? scr : argv[1];
624
+ dheap_t *heap = get_dheap_struct(self);
625
+ ENTRY entry;
626
+ rb_check_frozen(self);
246
627
 
247
- VALUE ary = DHEAP_GET_A(self);
248
- VALUE dval = DHEAP_GET_D(self);
249
- int d = FIX2INT(dval);
628
+ rb_check_arity(argc, 1, 2);
629
+ entry.value = argv[0];
630
+ entry.score = VAL2SCORE(argc < 2 ? entry.value : argv[1]);
631
+ dheap_push_entry(heap, &entry);
250
632
 
251
- return dheap_ary_push(ary, d, val, scr);
633
+ return self;
252
634
  }
253
635
 
254
636
  /*
255
- * Pushes a comparable value onto the heap.
637
+ * Pushes a value onto the heap.
256
638
  *
257
- * The value will be its own score.
639
+ * The score will be derived from the value, by using the value itself if it is
640
+ * an Integer, otherwise by casting it with +Float(value)+.
258
641
  *
259
642
  * Time complexity: <b>O(log n / log d)</b> <i>(worst-case)</i>
260
643
  *
261
- * @param value [#<=>] a value that can be compared to other heap members.
644
+ * @param value [Integer,#to_f] a value with an intrinsic numeric score
262
645
  * @return [self]
263
646
  */
264
647
  static VALUE
265
648
  dheap_left_shift(VALUE self, VALUE value) {
266
- dheap_push(1, &value, self);
649
+ dheap_t *heap = get_dheap_struct(self);
650
+ ENTRY entry;
651
+ rb_check_frozen(self);
652
+
653
+ entry.score = VAL2SCORE(value);
654
+ entry.value = value;
655
+ dheap_push_entry(heap, &entry);
656
+
267
657
  return self;
268
658
  }
269
659
 
270
- #define DHEAP_Pop_Init(self) \
271
- VALUE ary = DHEAP_GET_A(self); \
272
- VALUE dval = DHEAP_GET_D(self); \
273
- long last_index = DHEAP_LAST_IDX(ary); \
660
+ /********************************************************************
661
+ *
662
+ * DHeap pop and peek
663
+ *
664
+ ********************************************************************/
665
+
666
+ static inline void
667
+ dheap_del0(dheap_t *heap)
668
+ {
669
+ if (0 < --heap->size) {
670
+ heap->entries[0] = heap->entries[heap->size];
671
+ heap->entries[heap->size] = EmptyDheapEntry; // unnecessary to zero?
672
+ dheap_sift_down(heap, 0, heap->size - 1);
673
+ }
674
+ }
675
+
676
+ static inline VALUE
677
+ dheap_pop0(dheap_t *heap)
678
+ {
679
+ VALUE popped = DHEAP_VALUE(heap, 0);
680
+ dheap_del0(heap);
681
+ return popped;
682
+ }
683
+
684
+ /*
685
+ * Returns the next value on the heap, and its score, without popping it
686
+ *
687
+ * Time complexity: <b>O(1)</b> <i>(worst-case)</i>
688
+ * @return [nil,Array<(Object, Numeric)>] the next value and its score
689
+ *
690
+ * @see #peek
691
+ * @see #peek_score
692
+ */
693
+ static VALUE
694
+ dheap_peek_with_score(VALUE self)
695
+ {
696
+ dheap_t *heap = get_dheap_struct(self);
697
+ return DHEAP_ENTRY_ARY(heap, 0);
698
+ }
274
699
 
275
- #define DHEAP_Pop_SwapLastAndSiftDown(ary, dval, last_index, sift_value) \
276
- if (last_index == 0) { DHEAP_DROP_LAST(ary); return pop_value; } \
277
- VALUE sift_value = DHEAP_VALUE(ary, last_index); \
278
- VALUE sift_score = DHEAP_SCORE(ary, last_index); \
279
- DHEAP_ASSIGN(ary, 0, sift_score, sift_value); \
280
- DHEAP_DROP_LAST(ary); \
281
- dheap_ary_sift_down(ary, FIX2INT(dval), 0);
700
+ /*
701
+ * Returns the next score on the heap, without the value and without popping it.
702
+ *
703
+ * Time complexity: <b>O(1)</b> <i>(worst-case)</i>
704
+ * @return [nil, Numeric] the next score, if there is one
705
+ *
706
+ * @see #peek
707
+ * @see #peek_with_score
708
+ */
709
+ static VALUE
710
+ dheap_peek_score(VALUE self)
711
+ {
712
+ dheap_t *heap = get_dheap_struct(self);
713
+ if (DHEAP_IDX_LAST(heap) < 0) return Qnil;
714
+ return SCORE2NUM(DHEAP_SCORE(heap, 0));
715
+ }
282
716
 
283
717
  /*
284
718
  * Returns the next value on the heap to be popped without popping it.
285
719
  *
286
720
  * Time complexity: <b>O(1)</b> <i>(worst-case)</i>
287
- * @return [Object] the next value to be popped without popping it.
721
+ * @return [nil, Object] the next value to be popped without popping it.
722
+ *
723
+ * @see #peek_score
724
+ * @see #peek_with_score
288
725
  */
289
726
  static VALUE
290
- dheap_peek(VALUE self) {
291
- VALUE ary = DHEAP_GET_A(self);
292
- return DHEAP_VALUE(ary, 0);
727
+ dheap_peek(VALUE self)
728
+ {
729
+ dheap_t *heap = get_dheap_struct(self);
730
+ return heap->size ? DHEAP_VALUE(heap, 0) : Qnil;
293
731
  }
294
732
 
295
733
  /*
296
734
  * Pops the minimum value from the top of the heap
297
735
  *
298
736
  * Time complexity: <b>O(d log n / log d)</b> <i>(worst-case)</i>
737
+ *
738
+ * @return [Object] the value with the minimum score
739
+ *
740
+ * @see #peek
741
+ * @see #pop_lt
742
+ * @see #pop_lte
743
+ * @see #pop_with_score
299
744
  */
300
745
  static VALUE
301
- dheap_pop(VALUE self) {
302
- DHEAP_Pop_Init(self);
303
- if (last_index < 0) return Qnil;
304
- VALUE pop_value = DHEAP_VALUE(ary, 0);
746
+ dheap_pop(VALUE self)
747
+ {
748
+ dheap_t *heap = get_dheap_struct(self);
749
+ rb_check_frozen(self);
750
+ return heap->size ? dheap_pop0(heap) : Qnil;
751
+ }
305
752
 
306
- DHEAP_Pop_SwapLastAndSiftDown(ary, dval, last_index, sift_value);
307
- return pop_value;
753
+ /*
754
+ * Pops the minimum value from the top of the heap, along with its score.
755
+ *
756
+ * Time complexity: <b>O(d log n / log d)</b> <i>(worst-case)</i>
757
+ *
758
+ * @return [nil,Array<(Object, Numeric)>] the next value and its score
759
+ *
760
+ * @see #pop
761
+ * @see #peek_with_score
762
+ */
763
+ static VALUE
764
+ dheap_pop_with_score(VALUE self)
765
+ {
766
+ dheap_t *heap = get_dheap_struct(self);
767
+ VALUE ary = DHEAP_ENTRY_ARY(heap, 0);
768
+ rb_check_frozen(self);
769
+ if (ary != Qnil) { dheap_pop0(heap); }
770
+ return ary;
308
771
  }
309
772
 
310
773
  /*
311
774
  * Pops the minimum value only if it is less than or equal to a max score.
312
775
  *
313
- * @param max_score [#<=>] the maximum score to be popped
776
+ * @param max_score [Integer,#to_f] the maximum score to be popped
777
+ *
778
+ * Time complexity: <b>O(d log n / log d)</b> <i>(worst-case)</i>
779
+ *
780
+ * @return [Object] the value with the minimum score
314
781
  *
782
+ * @see #peek
315
783
  * @see #pop
784
+ * @see #pop_lt
785
+ * @see #pop_all_below
316
786
  */
317
787
  static VALUE
318
- dheap_pop_lte(VALUE self, VALUE max_score) {
319
- DHEAP_Pop_Init(self);
320
- if (last_index < 0) return Qnil;
321
- VALUE pop_value = DHEAP_VALUE(ary, 0);
322
-
323
- VALUE pop_score = DHEAP_SCORE(ary, 0);
324
- if (max_score && !CMP_LTE(pop_score, max_score)) return Qnil;
325
-
326
- DHEAP_Pop_SwapLastAndSiftDown(ary, dval, last_index, sift_value);
327
- return pop_value;
788
+ dheap_pop_lte(VALUE self, VALUE max_score)
789
+ {
790
+ dheap_t *heap = get_dheap_struct(self);
791
+ rb_check_frozen(self);
792
+ if (heap->size <= 0) return Qnil;
793
+ if (!CMP_LTE(DHEAP_SCORE(heap, 0), VAL2SCORE(max_score))) return Qnil;
794
+ return dheap_pop0(heap);
328
795
  }
329
796
 
330
797
  /*
331
798
  * Pops the minimum value only if it is less than a max score.
332
799
  *
333
- * @param max_score [#<=>] the maximum score to be popped
800
+ * @param max_score [Integer,#to_f] the maximum score to be popped
334
801
  *
335
802
  * Time complexity: <b>O(d log n / log d)</b> <i>(worst-case)</i>
803
+ *
804
+ * @return [Object] the value with the minimum score
805
+ *
806
+ * @see #peek
807
+ * @see #pop
808
+ * @see #pop_lte
809
+ * @see #pop_all_below
336
810
  */
337
811
  static VALUE
338
- dheap_pop_lt(VALUE self, VALUE max_score) {
339
- DHEAP_Pop_Init(self);
340
- if (last_index < 0) return Qnil;
341
- VALUE pop_value = DHEAP_VALUE(ary, 0);
812
+ dheap_pop_lt(VALUE self, VALUE max_score)
813
+ {
814
+ dheap_t *heap = get_dheap_struct(self);
815
+ rb_check_frozen(self);
816
+ if (heap->size <= 0) return Qnil;
817
+ if (!CMP_LT(DHEAP_SCORE(heap, 0), VAL2SCORE(max_score))) return Qnil;
818
+ return dheap_pop0(heap);
819
+ }
342
820
 
343
- VALUE pop_score = DHEAP_SCORE(ary, 0);
344
- if (max_score && !CMP_LT(pop_score, max_score)) return Qnil;
821
+ #define DHEAP_PEEK_LT_P(heap, max_score) \
822
+ (heap->size && CMP_LT(DHEAP_SCORE(heap, 0), max_score))
345
823
 
346
- DHEAP_Pop_SwapLastAndSiftDown(ary, dval, last_index, sift_value);
347
- return pop_value;
824
+ static VALUE
825
+ dheap_pop_all_below0(dheap_t *heap, SCORE max_score, VALUE array)
826
+ {
827
+ if (!RTEST(array)) { array = rb_ary_new(); }
828
+ if (RB_TYPE_P(array, T_ARRAY)) {
829
+ while (DHEAP_PEEK_LT_P(heap, max_score)) {
830
+ rb_ary_push(array, dheap_pop0(heap));
831
+ }
832
+ } else {
833
+ while (DHEAP_PEEK_LT_P(heap, max_score)) {
834
+ rb_funcall(array, id_lshift, 1, dheap_pop0(heap));
835
+ }
836
+ }
837
+ return array;
348
838
  }
349
839
 
350
- void
351
- Init_d_heap(void)
840
+ /*
841
+ * @overload pop_all_below(max_score, receiver = [])
842
+ *
843
+ * Pops all value with score less than max score.
844
+ *
845
+ * Time complexity: <b>O(m * d log n / log d)</b>, <i>m = number popped</i>
846
+ *
847
+ * @param max_score [Integer,#to_f] the maximum score to be popped
848
+ * @param receiver [Array,#<<] object onto which the values will be pushed,
849
+ * in order by score.
850
+ *
851
+ * @return [Object] the object onto which the values were pushed
852
+ *
853
+ * @see #pop
854
+ * @see #pop_lt
855
+ */
856
+ static VALUE
857
+ dheap_pop_all_below(int argc, VALUE *argv, VALUE self)
352
858
  {
353
- id_cmp = rb_intern_const("<=>");
354
- id_ivar_a = rb_intern_const("ary");
355
- id_ivar_d = rb_intern_const("d");
859
+ dheap_t *heap = get_dheap_struct(self);
860
+ SCORE max_score;
861
+ VALUE array;
862
+ rb_check_frozen(self);
863
+ rb_check_arity(argc, 1, 2);
864
+ max_score = VAL2SCORE(argv[0]);
865
+ if (argc == 1) {
866
+ array = rb_ary_new();
867
+ } else {
868
+ array = argv[1];
869
+ }
870
+ return dheap_pop_all_below0(heap, max_score, array);
871
+ }
356
872
 
357
- rb_cDHeap = rb_define_class("DHeap", rb_cObject);
358
- rb_define_const(rb_cDHeap, "MAX_D", INT2NUM(DHEAP_MAX_D));
359
- rb_define_const(rb_cDHeap, "DEFAULT_D", INT2NUM(DHEAP_DEFAULT_D));
873
+ /********************************************************************
874
+ *
875
+ * DHeap, misc methods
876
+ *
877
+ ********************************************************************/
360
878
 
361
- rb_define_singleton_method(rb_cDHeap, "heap_sift_down", dheap_sift_down_s, 3);
362
- rb_define_singleton_method(rb_cDHeap, "heap_sift_up", dheap_sift_up_s, 3);
879
+ /*
880
+ * Clears all values from the heap, leaving it empty.
881
+ *
882
+ * @return [self]
883
+ */
884
+ static VALUE
885
+ dheap_clear(VALUE self)
886
+ {
887
+ dheap_t *heap = get_dheap_struct(self);
888
+ rb_check_frozen(self);
889
+ if (0 < heap->size) {
890
+ heap->size = 0;
891
+ }
892
+ return self;
893
+ }
363
894
 
364
- rb_define_method(rb_cDHeap, "initialize", dheap_initialize, -1);
365
- rb_define_method(rb_cDHeap, "d", dheap_attr_d, 0);
366
- rb_define_method(rb_cDHeap, "freeze", dheap_freeze, 0);
895
+ /********************************************************************
896
+ *
897
+ * DHeap setup
898
+ *
899
+ ********************************************************************/
367
900
 
368
- rb_define_method(rb_cDHeap, "size", dheap_size, 0);
369
- rb_define_method(rb_cDHeap, "empty?", dheap_empty_p, 0);
901
+ void
902
+ Init_d_heap(void)
903
+ {
904
+ VALUE rb_cDHeap = rb_define_class("DHeap", rb_cObject);
370
905
 
906
+ id_cmp = rb_intern_const("<=>");
907
+ id_abs = rb_intern_const("abs");
908
+ id_lshift = rb_intern_const("<<");
909
+ id_unary_minus = rb_intern_const("-@");
910
+
911
+ rb_define_alloc_func(rb_cDHeap, dheap_s_alloc);
912
+
913
+ /*
914
+ * This is based on INT_MAX. But it is very very unlikely you will want a
915
+ * large value for d. The tradeoff is that higher d values give faster push
916
+ * and slower pop. If you expect pushes and pops to be balanced, then just
917
+ * stick with the default. If you expect more pushes than pops, it might be
918
+ * worthwhile to increase d.
919
+ */
920
+ rb_define_const(rb_cDHeap, "MAX_D", INT2NUM(DHEAP_MAX_D));
921
+
922
+ /*
923
+ * d=4 uses the fewest comparisons for (worst case) insert + delete-min.
924
+ */
925
+ rb_define_const(rb_cDHeap, "DEFAULT_D", INT2NUM(DHEAP_DEFAULT_D));
926
+
927
+ /*
928
+ * The default heap capacity. The heap grows automatically as necessary, so
929
+ * you shouldn't need to worry about this.
930
+ */
931
+ rb_define_const(rb_cDHeap, "DEFAULT_CAPA", INT2NUM(DHEAP_DEFAULT_CAPA));
932
+
933
+ rb_define_private_method(rb_cDHeap, "__init_without_kw__", dheap_initialize, 2);
934
+ rb_define_method(rb_cDHeap, "initialize_copy", dheap_initialize_copy, 1);
935
+
936
+ rb_define_method(rb_cDHeap, "d", dheap_attr_d, 0);
937
+ rb_define_method(rb_cDHeap, "size", dheap_size, 0);
938
+ rb_define_method(rb_cDHeap, "empty?", dheap_empty_p, 0);
371
939
  rb_define_method(rb_cDHeap, "peek", dheap_peek, 0);
940
+
941
+ rb_define_method(rb_cDHeap, "clear", dheap_clear, 0);
942
+ rb_define_method(rb_cDHeap, "insert", dheap_insert, 2);
372
943
  rb_define_method(rb_cDHeap, "push", dheap_push, -1);
373
944
  rb_define_method(rb_cDHeap, "<<", dheap_left_shift, 1);
374
945
  rb_define_method(rb_cDHeap, "pop", dheap_pop, 0);
375
- rb_define_method(rb_cDHeap, "pop_lt", dheap_pop_lt, 1);
376
- rb_define_method(rb_cDHeap, "pop_lte", dheap_pop_lte, 1);
946
+
947
+ rb_define_method(rb_cDHeap, "pop_lt", dheap_pop_lt, 1);
948
+ rb_define_method(rb_cDHeap, "pop_lte", dheap_pop_lte, 1);
949
+ rb_define_method(rb_cDHeap, "pop_all_below", dheap_pop_all_below, -1);
950
+
951
+ rb_define_method(rb_cDHeap, "peek_score", dheap_peek_score, 0);
952
+ rb_define_method(rb_cDHeap, "peek_with_score", dheap_peek_with_score, 0);
953
+ rb_define_method(rb_cDHeap, "pop_with_score", dheap_pop_with_score, 0);
954
+
377
955
  }