d_heap 0.5.0 → 0.6.0

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.
@@ -5,27 +5,31 @@ prelude: |
5
5
  include DHeap::Benchmarks
6
6
  fill_random_vals
7
7
 
8
- n = ENV.fetch("BENCH_N", 1000).to_i
8
+ N = ENV.fetch("BENCH_N", 1000).to_i
9
9
 
10
10
  benchmark:
11
11
  - script: &script |
12
- q.clear
13
- i = 0
14
- while i < n
15
- q << random_val
16
- i += 1
12
+ if __bmdv_i % N == 0
13
+ q.clear
17
14
  end
15
+
16
+ q << random_val
18
17
  name: "push N (findmin)"
19
18
  prelude: "q = initq FindMin"
19
+ loop_count: 24000000
20
20
  - script: *script
21
21
  name: "push N (bsearch)"
22
22
  prelude: "q = initq BSearch"
23
+ loop_count: 2300000
23
24
  - script: *script
24
25
  name: "push N (rb_heap)"
25
26
  prelude: "q = initq RbHeap"
27
+ loop_count: 9800000
26
28
  - script: *script
27
29
  name: "push N (c++ stl)"
28
30
  prelude: "q = initq CppSTL"
31
+ loop_count: 18700000
29
32
  - script: *script
30
33
  name: "push N (c_dheap)"
31
34
  prelude: "q = initq DHeap"
35
+ loop_count: 25100000
@@ -5,31 +5,48 @@ prelude: |
5
5
  include DHeap::Benchmarks
6
6
  fill_random_vals
7
7
 
8
- n = ENV.fetch("BENCH_N", 1000).to_i
8
+ N = ENV.fetch("BENCH_N", 1000).to_i
9
+ N2 = N * 2
9
10
 
10
- i = 0
11
+ i = j = 0
11
12
 
12
13
  benchmark:
13
14
  - script: &script |
14
- while i < n
15
- q << random_val
16
- i += 1
17
- end
18
- while 0 < i
19
- q.pop
20
- i -= 1
21
- end
15
+ if i < N
16
+ q.clear if __bmdv_i == 0
17
+ q << random_val
18
+ i += 1
19
+
20
+ elsif j < N
21
+ q.pop
22
+ j += 1
23
+
24
+ elsif q.empty?
25
+ i = 1
26
+ j = 0
27
+ q.clear
28
+ q << random_val
29
+
30
+ else
31
+ raise "q not empty!"
32
+ end
33
+
22
34
  name: "push N + pop N (findmin)"
23
35
  prelude: "q = initq FindMin"
36
+ loop_count: 200000
24
37
  - script: *script
25
38
  name: "push N + pop N (bsearch)"
26
39
  prelude: "q = initq BSearch"
40
+ loop_count: 4000000
27
41
  - script: *script
28
42
  name: "push N + pop N (rb_heap)"
29
43
  prelude: "q = initq RbHeap"
44
+ loop_count: 4000000
30
45
  - script: *script
31
46
  name: "push N + pop N (c++ stl)"
32
47
  prelude: "q = initq CppSTL"
48
+ loop_count: 16000000
33
49
  - script: *script
34
50
  name: "push N + pop N (c_dheap)"
35
51
  prelude: "q = initq DHeap"
52
+ loop_count: 16000000
@@ -13,15 +13,20 @@ benchmark:
13
13
  q.pop
14
14
  name: "push + pop (findmin)"
15
15
  prelude: "q = FindMin.new(n) { random_val }"
16
+ loop_count: 250000
16
17
  - script: *script
17
18
  name: "push + pop (bsearch)"
18
19
  prelude: "q = BSearch.new(n) { random_val }"
20
+ loop_count: 5000000
19
21
  - script: *script
20
22
  name: "push + pop (rb_heap)"
21
23
  prelude: "q = RbHeap.new(n) { random_val }"
24
+ loop_count: 2000000
22
25
  - script: *script
23
26
  name: "push + pop (c++ stl)"
24
27
  prelude: "q = initq CppSTL, n"
28
+ loop_count: 13000000
25
29
  - script: *script
26
30
  name: "push + pop (c_dheap)"
27
31
  prelude: "q = initq DHeap, n"
32
+ loop_count: 20000000
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+ set -eu
3
+ SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null; pwd -P)
4
+ PROJECT_DIR=$(cd "$SCRIPT_DIR" > /dev/null; cd .. > /dev/null; pwd -P)
5
+ cd "$PROJECT_DIR"
6
+
7
+ opts=( --bundle -e 'N 100000;N 10000;N 1000;N 100;N 10' -o gruff )
8
+
9
+ for bm in push_n push_n_pop_n push_pop; do
10
+ bin/benchmark-driver "${opts[@]}" "benchmarks/${bm}.yml"
11
+ mv graph.png "images/${bm}.png"
12
+ done
13
+
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
15
15
  DESC
16
16
  spec.homepage = "https://github.com/nevans/#{spec.name}"
17
17
  spec.license = "MIT"
18
- spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
18
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
19
19
 
20
20
  spec.metadata["homepage_uri"] = spec.homepage
21
21
  spec.metadata["source_code_uri"] = spec.homepage
@@ -1,36 +1,96 @@
1
1
  #include <float.h>
2
- #include "d_heap.h"
2
+ #include <math.h>
3
+ #include "ruby.h"
3
4
 
4
- ID id_cmp; // <=>
5
- ID id_abs; // abs
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;
6
16
 
7
- typedef struct dheap_struct {
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 {
8
33
  int d;
9
34
  long size;
10
35
  long capa;
11
36
  ENTRY *entries;
12
- } dheap_t;
37
+ };
13
38
 
14
- #define DHEAP_SCORE(heap, idx) ((heap)->entries[idx].score)
15
- #define DHEAP_VALUE(heap, idx) ((heap)->entries[idx].value)
16
- #define DHEAP_IDX_LAST(heap) (DHEAP_SIZE((heap)) - 1)
17
- #define DHEAP_IDX_PARENT(heap, idx) (((idx) - 1) / (heap)->d)
18
- #define DHEAP_IDX_CHILD_0(heap, idx) (((idx) * (heap)->d) + 1)
19
- #define DHEAP_IDX_CHILD_D(heap, idx) (((idx) * (heap)->d) + (heap)->d)
39
+ struct dheap_entry {
40
+ SCORE score;
41
+ VALUE value;
42
+ };
20
43
 
21
- #define DHEAP_SIZE(heap) ((heap)->size)
44
+ /********************************************************************
45
+ *
46
+ * Constant definitions
47
+ *
48
+ ********************************************************************/
22
49
 
23
- #define CMP_LT(a, b) ((a) < (b))
24
- #define CMP_LTE(a, b) ((a) <= (b))
25
- #define CMP_GT(a, b) ((a) > (b))
26
- #define CMP_GTE(a, b) ((a) >= (b))
50
+ #define DHEAP_DEFAULT_D 4
51
+ #define DHEAP_MAX_D INT_MAX
27
52
 
28
- #if LDBL_MANT_DIG < SIZEOF_UNSIGNED_LONG_LONG * 8
29
- #error 'unsigned long long' should fit into 'long double' mantissa
30
- #endif
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));
84
+ }
85
+ return rb_float_new((double)(s));
86
+ }
87
+
88
+ static inline SCORE
89
+ FIX2SCORE(VALUE x)
90
+ {
91
+ return (long double)FIX2LONG(x);
92
+ }
31
93
 
32
- // copied and modified from ruby's object.c
33
- #define FIX2SCORE(x) (long double)FIX2LONG(x)
34
94
  // We could translate a much wider range of values to long double by
35
95
  // implementing a new `rb_big2ldbl(x)` function. But requires reaching into
36
96
  // T_BIGNUM internals.
@@ -43,18 +103,27 @@ BIG2SCORE(VALUE x)
43
103
  } else {
44
104
  unsigned long long ull;
45
105
  long double ldbl;
46
- x = rb_funcall(x, id_abs, 0);
106
+ x = rb_funcall(x, id_abs, 0, Qundef);
47
107
  ull = rb_big2ull(x);
48
108
  ldbl = (long double) ull;
49
109
  return -ldbl;
50
110
  }
51
111
  }
52
- #define INT2SCORE(x) \
53
- (FIXNUM_P(x) ? FIX2SCORE(x) : BIG2SCORE(x))
54
- #define NUM2SCORE(x) \
55
- (FIXNUM_P(x) ? FIX2SCORE(x) : \
56
- RB_TYPE_P(x, T_BIGNUM) ? BIG2SCORE(x) : \
57
- (Check_Type(x, T_FLOAT), (long double)RFLOAT_VALUE(x)))
112
+
113
+ static inline SCORE
114
+ INT2SCORE(VALUE x)
115
+ {
116
+ return (FIXNUM_P(x) ? FIX2SCORE(x) : BIG2SCORE(x));
117
+ }
118
+
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
+ }
126
+
58
127
  static inline long double
59
128
  RAT2SCORE(VALUE x)
60
129
  {
@@ -73,14 +142,13 @@ RAT2SCORE(VALUE x)
73
142
  * * reduced to long double (should be 80 or 128 bit) if it is Rational
74
143
  * * reduced to double precision if the value is convertable by Float(x)
75
144
  */
76
- static inline long double
145
+ static inline SCORE
77
146
  VAL2SCORE(VALUE score)
78
147
  {
79
148
  // assert that long double can hold 'unsigned long long':
80
149
  // static_assert(sizeof(unsigned long long) * 8 <= LDBL_MANT_DIG);
81
150
  // assert that long double can hold T_FLOAT
82
151
  // static_assert(sizeof(double) <= sizeof(long double));
83
-
84
152
  switch (TYPE(score)) {
85
153
  case T_FIXNUM:
86
154
  return FIX2SCORE(score);
@@ -93,59 +161,37 @@ VAL2SCORE(VALUE score)
93
161
  }
94
162
  }
95
163
 
96
- #ifdef ORIG_SCORE_CMP_CODE
97
-
98
- #define VAL2SCORE(score) (score)
99
-
100
- #define CMP_LT(a, b) (optimized_cmp(a, b) < 0)
101
- #define CMP_LTE(a, b) (optimized_cmp(a, b) <= 0)
102
- #define CMP_GT(a, b) (optimized_cmp(a, b) > 0)
103
- #define CMP_GTE(a, b) (optimized_cmp(a, b) >= 0)
104
-
105
- // from internal/compar.h
106
- #define STRING_P(s) (RB_TYPE_P((s), T_STRING) && CLASS_OF(s) == rb_cString)
107
- /*
108
- * short-circuit evaluation for a few basic types.
164
+ /********************************************************************
109
165
  *
110
- * Only Integer, Float, and String are optimized,
111
- * and only when both arguments are the same type.
112
- */
113
- static inline int
114
- optimized_cmp(SCORE a, SCORE b) {
115
- if (a == b) // Fixnum equality and object equality
116
- return 0;
117
- if (FIXNUM_P(a) && FIXNUM_P(b))
118
- return (FIX2LONG(a) < FIX2LONG(b)) ? -1 : 1;
119
- if (RB_FLOAT_TYPE_P(a) && RB_FLOAT_TYPE_P(b))
120
- {
121
- double x, y;
122
- x = RFLOAT_VALUE(a);
123
- y = RFLOAT_VALUE(b);
124
- if (isnan(x) || isnan(y)) rb_cmperr(a, b); // raise ArgumentError
125
- return (x < y) ? -1 : ((x == y) ? 0 : 1);
126
- }
127
- if (RB_TYPE_P(a, T_BIGNUM) && RB_TYPE_P(b, T_BIGNUM))
128
- return FIX2INT(rb_big_cmp(a, b));
129
- if (STRING_P(a) && STRING_P(b))
130
- return rb_str_cmp(a, b);
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)
131
172
 
132
- // give up on an optimized version and just call (a <=> b)
133
- return rb_cmpint(rb_funcallv(a, id_cmp, 1, &b), a, b);
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)));
134
180
  }
135
181
 
136
- #endif
182
+ /********************************************************************
183
+ *
184
+ * DHeap index math
185
+ *
186
+ ********************************************************************/
137
187
 
138
- #define DHEAP_Check_d_size(d) do { \
139
- if (d < 2) { \
140
- rb_raise(rb_eArgError, "DHeap d=%d is too small", d); \
141
- } \
142
- if (d > DHEAP_MAX_D) { \
143
- rb_raise(rb_eArgError, "DHeap d=%d is too large", d); \
144
- } \
145
- } while (0)
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)
146
192
 
147
193
  #ifdef __D_HEAP_DEBUG
148
- #define ASSERT_DHEAP_INDEX(heap, index) do { \
194
+ #define ASSERT_DHEAP_IDX_OK(heap, index) do { \
149
195
  if (index < 0) { \
150
196
  rb_raise(rb_eIndexError, "DHeap index %ld too small", index); \
151
197
  } \
@@ -154,18 +200,28 @@ optimized_cmp(SCORE a, SCORE b) {
154
200
  } \
155
201
  } while (0)
156
202
  #else
157
- #define ASSERT_DHEAP_INDEX(heap, index)
203
+ #define ASSERT_DHEAP_IDX_OK(heap, index)
158
204
  #endif
159
205
 
206
+ /********************************************************************
207
+ *
208
+ * rb_data_type_t definitions
209
+ *
210
+ ********************************************************************/
211
+
212
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
160
213
  static void
161
214
  dheap_compact(void *ptr)
162
215
  {
163
216
  dheap_t *heap = ptr;
164
217
  for (long i = 0; i < heap->size; ++i) {
165
218
  if (DHEAP_VALUE(heap, i))
166
- dheap_gc_location(DHEAP_VALUE(heap, i));
219
+ rb_gc_location(DHEAP_VALUE(heap, i));
167
220
  }
168
221
  }
222
+ #else
223
+ #define rb_gc_mark_movable(x) rb_gc_mark(x)
224
+ #endif
169
225
 
170
226
  static void
171
227
  dheap_mark(void *ptr)
@@ -200,25 +256,88 @@ dheap_memsize(const void *ptr)
200
256
  return size;
201
257
  }
202
258
 
259
+
203
260
  static const rb_data_type_t dheap_data_type = {
204
261
  "DHeap",
205
262
  {
206
263
  (void (*)(void*))dheap_mark,
207
264
  (void (*)(void*))dheap_free,
208
265
  (size_t (*)(const void *))dheap_memsize,
209
- dheap_compact_callback(dheap_compact),
266
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
267
+ (void (*)(void*))dheap_compact, {0}
268
+ #else
269
+ {0}
270
+ #endif
210
271
  },
211
272
  0, 0,
212
273
  RUBY_TYPED_FREE_IMMEDIATELY,
213
274
  };
214
275
 
276
+ /********************************************************************
277
+ *
278
+ * DHeap comparisons
279
+ * TODO: bring back comparisons for score types other than `long double`.
280
+ *
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.
295
+ *
296
+ * Only Integer, Float, and String are optimized,
297
+ * and only when both arguments are the same type.
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
+
215
330
  static VALUE
216
331
  dheap_s_alloc(VALUE klass)
217
332
  {
218
333
  VALUE obj;
219
334
  dheap_t *heap;
220
335
 
336
+ // TypedData_Make_Struct uses a non-std "statement expression"
337
+ #pragma GCC diagnostic push
338
+ #pragma GCC diagnostic ignored "-Wpedantic"
221
339
  obj = TypedData_Make_Struct(klass, dheap_t, &dheap_data_type, heap);
340
+ #pragma GCC diagnostic pop
222
341
  heap->d = DHEAP_DEFAULT_D;
223
342
 
224
343
  heap->size = 0;
@@ -257,7 +376,7 @@ dheap_ensure_room_for_push(dheap_t *heap, long incr_by)
257
376
  long new_size = heap->size + incr_by;
258
377
 
259
378
  // check for overflow of new_size
260
- if (DHEAP_MAX_SIZE - incr_by < heap->size)
379
+ if (DHEAP_MAX_CAPA - incr_by < heap->size)
261
380
  rb_raise(rb_eIndexError, "index %ld too big", new_size);
262
381
 
263
382
  // if it existing capacity is too small
@@ -267,7 +386,7 @@ dheap_ensure_room_for_push(dheap_t *heap, long incr_by)
267
386
  if (DHEAP_CAPA_INCR_MAX < new_size)
268
387
  new_size = new_size + DHEAP_CAPA_INCR_MAX;
269
388
  // check for overflow of new_capa
270
- if (DHEAP_MAX_SIZE / 2 < new_size) new_capa = DHEAP_MAX_SIZE;
389
+ if (DHEAP_MAX_CAPA / 2 < new_size) new_capa = DHEAP_MAX_CAPA;
271
390
  // cap max incr_by
272
391
  if (heap->capa + DHEAP_CAPA_INCR_MAX < new_capa)
273
392
  new_capa = heap->capa + DHEAP_CAPA_INCR_MAX;
@@ -276,25 +395,38 @@ dheap_ensure_room_for_push(dheap_t *heap, long incr_by)
276
395
  }
277
396
  }
278
397
 
279
- /*
280
- * @overload initialize(d = DHeap::DEFAULT_D)
281
- * Initialize a _d_-ary min-heap.
282
- *
283
- * @param d [Integer] maximum number of children per parent
284
- */
285
- static VALUE
286
- dheap_initialize(int argc, VALUE *argv, VALUE self) {
287
- dheap_t *heap;
288
- int d;
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
+ }
289
410
 
290
- rb_check_arity(argc, 0, 1);
291
- TypedData_Get_Struct(self, dheap_t, &dheap_data_type, heap);
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
+ }
292
420
 
293
- d = argc ? NUM2INT(argv[0]) : DHEAP_DEFAULT_D;
294
- DHEAP_Check_d_size(d);
295
- heap->d = d;
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.");
296
427
 
297
- dheap_set_capa(heap, DHEAP_DEFAULT_SIZE);
428
+ heap->d = dheap_value_to_int_d(d);
429
+ dheap_set_capa(heap, dheap_value_to_long_capa(capa));
298
430
 
299
431
  return self;
300
432
  }
@@ -319,11 +451,17 @@ dheap_initialize_copy(VALUE copy, VALUE orig)
319
451
  return copy;
320
452
  }
321
453
 
454
+ /********************************************************************
455
+ *
456
+ * DHeap sift up/down
457
+ *
458
+ ********************************************************************/
459
+
322
460
  VALUE
323
461
  dheap_sift_up(dheap_t *heap, long index) {
324
462
  ENTRY entry = heap->entries[index];
325
463
 
326
- ASSERT_DHEAP_INDEX(heap, index);
464
+ ASSERT_DHEAP_IDX_OK(heap, index);
327
465
 
328
466
  // sift it up to where it belongs
329
467
  for (long parent_index; 0 < index; index = parent_index) {
@@ -370,7 +508,7 @@ dheap_sift_down(dheap_t *heap, long index, long last_index) {
370
508
 
371
509
  long last_parent = DHEAP_IDX_PARENT(heap, last_index);
372
510
 
373
- ASSERT_DHEAP_INDEX(heap, index);
511
+ ASSERT_DHEAP_IDX_OK(heap, index);
374
512
 
375
513
  // iteratively sift it down to where it belongs
376
514
  while (index <= last_parent) {
@@ -389,6 +527,12 @@ dheap_sift_down(dheap_t *heap, long index, long last_index) {
389
527
  }
390
528
  }
391
529
 
530
+ /********************************************************************
531
+ *
532
+ * DHeap attributes
533
+ *
534
+ ********************************************************************/
535
+
392
536
  /*
393
537
  * @return [Integer] the number of elements in the heap
394
538
  */
@@ -396,8 +540,7 @@ static VALUE
396
540
  dheap_size(VALUE self)
397
541
  {
398
542
  dheap_t *heap = get_dheap_struct(self);
399
- long size = DHEAP_SIZE(heap);
400
- return LONG2NUM(size);
543
+ return LONG2NUM(heap->size);
401
544
  }
402
545
 
403
546
  /*
@@ -407,8 +550,7 @@ static VALUE
407
550
  dheap_empty_p(VALUE self)
408
551
  {
409
552
  dheap_t *heap = get_dheap_struct(self);
410
- long size = DHEAP_SIZE(heap);
411
- return size == 0 ? Qtrue : Qfalse;
553
+ return heap->size ? Qfalse : Qtrue;
412
554
  }
413
555
 
414
556
  /*
@@ -421,16 +563,11 @@ dheap_attr_d(VALUE self)
421
563
  return INT2FIX(heap->d);
422
564
  }
423
565
 
424
- /* @!visibility private */
425
- static VALUE
426
- dheap_init_clone(VALUE clone, VALUE orig, VALUE kwfreeze)
427
- {
428
- dheap_initialize_copy(clone, orig);
429
- if (RTEST(kwfreeze) || (kwfreeze == Qnil && OBJ_FROZEN(orig))) {
430
- rb_funcall(clone, rb_intern("freeze"), 0);
431
- }
432
- return clone;
433
- }
566
+ /********************************************************************
567
+ *
568
+ * DHeap push
569
+ *
570
+ ********************************************************************/
434
571
 
435
572
  static inline void
436
573
  dheap_push_entry(dheap_t *heap, ENTRY *entry) {
@@ -520,47 +657,77 @@ dheap_left_shift(VALUE self, VALUE value) {
520
657
  return self;
521
658
  }
522
659
 
523
- static const ENTRY EmptyDheapEntry;
660
+ /********************************************************************
661
+ *
662
+ * DHeap pop and peek
663
+ *
664
+ ********************************************************************/
524
665
 
525
- static inline VALUE
526
- dheap_pop0(dheap_t *heap)
666
+ static inline void
667
+ dheap_del0(dheap_t *heap)
527
668
  {
528
- VALUE popped = DHEAP_VALUE(heap, 0);
529
669
  if (0 < --heap->size) {
530
670
  heap->entries[0] = heap->entries[heap->size];
531
671
  heap->entries[heap->size] = EmptyDheapEntry; // unnecessary to zero?
532
672
  dheap_sift_down(heap, 0, heap->size - 1);
533
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);
534
681
  return popped;
535
682
  }
536
683
 
537
684
  /*
538
- * Clears all values from the heap, leaving it empty.
685
+ * Returns the next value on the heap, and its score, without popping it
539
686
  *
540
- * @return [self]
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
541
692
  */
542
693
  static VALUE
543
- dheap_clear(VALUE self) {
694
+ dheap_peek_with_score(VALUE self)
695
+ {
544
696
  dheap_t *heap = get_dheap_struct(self);
545
- rb_check_frozen(self);
546
- if (0 < DHEAP_SIZE(heap)) {
547
- heap->size = 0;
548
- }
549
- return self;
697
+ return DHEAP_ENTRY_ARY(heap, 0);
698
+ }
699
+
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));
550
715
  }
551
716
 
552
717
  /*
553
718
  * Returns the next value on the heap to be popped without popping it.
554
719
  *
555
720
  * Time complexity: <b>O(1)</b> <i>(worst-case)</i>
556
- * @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
557
725
  */
558
726
  static VALUE
559
727
  dheap_peek(VALUE self)
560
728
  {
561
729
  dheap_t *heap = get_dheap_struct(self);
562
- if (DHEAP_IDX_LAST(heap) < 0) return Qnil;
563
- return DHEAP_VALUE(heap, 0);
730
+ return heap->size ? DHEAP_VALUE(heap, 0) : Qnil;
564
731
  }
565
732
 
566
733
  /*
@@ -573,14 +740,34 @@ dheap_peek(VALUE self)
573
740
  * @see #peek
574
741
  * @see #pop_lt
575
742
  * @see #pop_lte
743
+ * @see #pop_with_score
576
744
  */
577
745
  static VALUE
578
746
  dheap_pop(VALUE self)
579
747
  {
580
748
  dheap_t *heap = get_dheap_struct(self);
581
749
  rb_check_frozen(self);
582
- if (DHEAP_SIZE(heap) <= 0) return Qnil;
583
- return dheap_pop0(heap);
750
+ return heap->size ? dheap_pop0(heap) : Qnil;
751
+ }
752
+
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;
584
771
  }
585
772
 
586
773
  /*
@@ -595,13 +782,14 @@ dheap_pop(VALUE self)
595
782
  * @see #peek
596
783
  * @see #pop
597
784
  * @see #pop_lt
785
+ * @see #pop_all_below
598
786
  */
599
787
  static VALUE
600
788
  dheap_pop_lte(VALUE self, VALUE max_score)
601
789
  {
602
790
  dheap_t *heap = get_dheap_struct(self);
603
791
  rb_check_frozen(self);
604
- if (DHEAP_SIZE(heap) <= 0) return Qnil;
792
+ if (heap->size <= 0) return Qnil;
605
793
  if (!CMP_LTE(DHEAP_SCORE(heap, 0), VAL2SCORE(max_score))) return Qnil;
606
794
  return dheap_pop0(heap);
607
795
  }
@@ -618,32 +806,132 @@ dheap_pop_lte(VALUE self, VALUE max_score)
618
806
  * @see #peek
619
807
  * @see #pop
620
808
  * @see #pop_lte
809
+ * @see #pop_all_below
621
810
  */
622
811
  static VALUE
623
812
  dheap_pop_lt(VALUE self, VALUE max_score)
624
813
  {
625
814
  dheap_t *heap = get_dheap_struct(self);
626
815
  rb_check_frozen(self);
627
- if (DHEAP_SIZE(heap) <= 0) return Qnil;
816
+ if (heap->size <= 0) return Qnil;
628
817
  if (!CMP_LT(DHEAP_SCORE(heap, 0), VAL2SCORE(max_score))) return Qnil;
629
818
  return dheap_pop0(heap);
630
819
  }
631
820
 
821
+ #define DHEAP_PEEK_LT_P(heap, max_score) \
822
+ (heap->size && CMP_LT(DHEAP_SCORE(heap, 0), max_score))
823
+
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;
838
+ }
839
+
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)
858
+ {
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
+ }
872
+
873
+ /********************************************************************
874
+ *
875
+ * DHeap, misc methods
876
+ *
877
+ ********************************************************************/
878
+
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
+ }
894
+
895
+ /********************************************************************
896
+ *
897
+ * DHeap setup
898
+ *
899
+ ********************************************************************/
900
+
632
901
  void
633
902
  Init_d_heap(void)
634
903
  {
904
+ VALUE rb_cDHeap = rb_define_class("DHeap", rb_cObject);
905
+
635
906
  id_cmp = rb_intern_const("<=>");
636
907
  id_abs = rb_intern_const("abs");
908
+ id_lshift = rb_intern_const("<<");
909
+ id_unary_minus = rb_intern_const("-@");
637
910
 
638
- rb_cDHeap = rb_define_class("DHeap", rb_cObject);
639
911
  rb_define_alloc_func(rb_cDHeap, dheap_s_alloc);
640
912
 
641
- rb_define_const(rb_cDHeap, "MAX_D", INT2NUM(DHEAP_MAX_D));
642
- rb_define_const(rb_cDHeap, "DEFAULT_D", INT2NUM(DHEAP_DEFAULT_D));
643
-
644
- rb_define_method(rb_cDHeap, "initialize", dheap_initialize, -1);
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);
645
934
  rb_define_method(rb_cDHeap, "initialize_copy", dheap_initialize_copy, 1);
646
- rb_define_private_method(rb_cDHeap, "__init_clone__", dheap_init_clone, 2);
647
935
 
648
936
  rb_define_method(rb_cDHeap, "d", dheap_attr_d, 0);
649
937
  rb_define_method(rb_cDHeap, "size", dheap_size, 0);
@@ -655,6 +943,13 @@ Init_d_heap(void)
655
943
  rb_define_method(rb_cDHeap, "push", dheap_push, -1);
656
944
  rb_define_method(rb_cDHeap, "<<", dheap_left_shift, 1);
657
945
  rb_define_method(rb_cDHeap, "pop", dheap_pop, 0);
658
- rb_define_method(rb_cDHeap, "pop_lt", dheap_pop_lt, 1);
659
- 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
+
660
955
  }