disjoint_interval_tree 0.1.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.
- checksums.yaml +7 -0
- data/CITATION.cff +67 -0
- data/README.md +340 -0
- data/Rakefile +232 -0
- data/disjoint_interval_tree.gemspec +42 -0
- data/ext/disjoint_interval_tree/dit.c +850 -0
- data/ext/disjoint_interval_tree/dit.h +244 -0
- data/ext/disjoint_interval_tree/dit_ruby.c +648 -0
- data/ext/disjoint_interval_tree/extconf.rb +17 -0
- data/lib/disjoint_interval_tree/version.rb +5 -0
- data/lib/disjoint_interval_tree.rb +52 -0
- data/test/c/Makefile +58 -0
- data/test/c/test_dit.c +1072 -0
- data/test/test_disjoint_interval_tree.rb +649 -0
- metadata +65 -0
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** Copyright 2025 INRIA
|
|
3
|
+
**
|
|
4
|
+
** Contributors :
|
|
5
|
+
** Romain PEREIRA, romain.pereira@inria.fr + rpereira@anl.gov
|
|
6
|
+
**
|
|
7
|
+
** This software is governed by the CeCILL-C license under French law and
|
|
8
|
+
** abiding by the rules of distribution of free software. You can use,
|
|
9
|
+
** modify and/ or redistribute the software under the terms of the CeCILL-C
|
|
10
|
+
** license as circulated by CEA, CNRS and INRIA at the following URL
|
|
11
|
+
** "http://www.cecill.info".
|
|
12
|
+
**
|
|
13
|
+
** The fact that you are presently reading this means that you have had
|
|
14
|
+
** knowledge of the CeCILL-C license and that you accept its terms.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/* Ruby bindings for the disjoint interval tree */
|
|
18
|
+
|
|
19
|
+
#include <ruby.h>
|
|
20
|
+
|
|
21
|
+
#include <inttypes.h>
|
|
22
|
+
|
|
23
|
+
#include "dit.h"
|
|
24
|
+
|
|
25
|
+
static VALUE cTree;
|
|
26
|
+
static VALUE eError;
|
|
27
|
+
static VALUE eOverlapError;
|
|
28
|
+
static VALUE eCorruptedError;
|
|
29
|
+
|
|
30
|
+
static VALUE rb_dit_size(VALUE self);
|
|
31
|
+
|
|
32
|
+
/* not exposed as such by every supported ruby version */
|
|
33
|
+
#ifndef RBIGNUM_NEGATIVE_P
|
|
34
|
+
# define RBIGNUM_NEGATIVE_P(B) (rb_big_sign(B) == 0)
|
|
35
|
+
#endif /* RBIGNUM_NEGATIVE_P */
|
|
36
|
+
|
|
37
|
+
/////////////////////
|
|
38
|
+
// TYPED DATA GLUE //
|
|
39
|
+
/////////////////////
|
|
40
|
+
|
|
41
|
+
static void
|
|
42
|
+
rb_dit_free(void * ptr)
|
|
43
|
+
{
|
|
44
|
+
dit_t * tree = (dit_t *) ptr;
|
|
45
|
+
|
|
46
|
+
/* a traversal cannot be in progress here: the tree is unreachable */
|
|
47
|
+
tree->traversing = 0;
|
|
48
|
+
dit_destroy(tree);
|
|
49
|
+
xfree(tree);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static size_t
|
|
53
|
+
rb_dit_memsize(const void * ptr)
|
|
54
|
+
{
|
|
55
|
+
const dit_t * tree = (const dit_t *) ptr;
|
|
56
|
+
return sizeof(dit_t) + tree->n * sizeof(dit_node_t);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static const rb_data_type_t rb_dit_type = {
|
|
60
|
+
.wrap_struct_name = "DisjointIntervalTree",
|
|
61
|
+
.function = {
|
|
62
|
+
.dmark = NULL,
|
|
63
|
+
.dfree = rb_dit_free,
|
|
64
|
+
.dsize = rb_dit_memsize,
|
|
65
|
+
},
|
|
66
|
+
.parent = NULL,
|
|
67
|
+
.data = NULL,
|
|
68
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
static inline dit_t *
|
|
72
|
+
rb_dit_get(VALUE self)
|
|
73
|
+
{
|
|
74
|
+
dit_t * tree;
|
|
75
|
+
TypedData_Get_Struct(self, dit_t, &rb_dit_type, tree);
|
|
76
|
+
return tree;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Mutating the tree from within `each`/`intersect` would invalidate the
|
|
80
|
+
* traversal cursor, and possibly free the node being visited */
|
|
81
|
+
static inline dit_t *
|
|
82
|
+
rb_dit_get_mutable(VALUE self)
|
|
83
|
+
{
|
|
84
|
+
dit_t * tree = rb_dit_get(self);
|
|
85
|
+
|
|
86
|
+
rb_check_frozen(self);
|
|
87
|
+
|
|
88
|
+
if (tree->traversing)
|
|
89
|
+
rb_raise(eError, "cannot modify a DisjointIntervalTree while traversing it");
|
|
90
|
+
|
|
91
|
+
return tree;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Interval bounds are unsigned 64 bits integers.
|
|
95
|
+
*
|
|
96
|
+
* `NUM2ULL` cannot be used as-is: it mirrors C conversion rules and silently
|
|
97
|
+
* wraps negative values around, which would turn a `-1` typo into a
|
|
98
|
+
* `18446744073709551615` bound. Floats are rejected too, as truncating them
|
|
99
|
+
* silently would be just as surprising */
|
|
100
|
+
static inline dit_value_t
|
|
101
|
+
rb_dit_value(VALUE v)
|
|
102
|
+
{
|
|
103
|
+
if (RB_FIXNUM_P(v))
|
|
104
|
+
{
|
|
105
|
+
const long l = FIX2LONG(v);
|
|
106
|
+
if (l < 0)
|
|
107
|
+
rb_raise(rb_eRangeError,
|
|
108
|
+
"interval bounds must be in [0..2**64[, got %ld", l);
|
|
109
|
+
return (dit_value_t) l;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (RB_TYPE_P(v, T_BIGNUM))
|
|
113
|
+
{
|
|
114
|
+
if (RBIGNUM_NEGATIVE_P(v))
|
|
115
|
+
rb_raise(rb_eRangeError,
|
|
116
|
+
"interval bounds must be in [0..2**64[, got %"PRIsVALUE, v);
|
|
117
|
+
|
|
118
|
+
/* raises RangeError if it does not fit in 64 bits */
|
|
119
|
+
return (dit_value_t) rb_big2ull(v);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
rb_raise(rb_eTypeError, "no implicit conversion of %"PRIsVALUE" into Integer",
|
|
123
|
+
rb_obj_class(v));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static inline VALUE
|
|
127
|
+
rb_dit_interval(dit_value_t a, dit_value_t b)
|
|
128
|
+
{
|
|
129
|
+
return rb_assoc_new(ULL2NUM(a), ULL2NUM(b));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static VALUE
|
|
133
|
+
rb_dit_alloc(VALUE klass)
|
|
134
|
+
{
|
|
135
|
+
dit_t * tree;
|
|
136
|
+
VALUE self = TypedData_Make_Struct(klass, dit_t, &rb_dit_type, tree);
|
|
137
|
+
dit_init(tree);
|
|
138
|
+
return self;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
////////////////
|
|
142
|
+
// CALLBACKS //
|
|
143
|
+
////////////////
|
|
144
|
+
|
|
145
|
+
static int
|
|
146
|
+
rb_dit_cb_yield(dit_value_t a, dit_value_t b, void * user)
|
|
147
|
+
{
|
|
148
|
+
(void) user;
|
|
149
|
+
rb_yield_values(2, ULL2NUM(a), ULL2NUM(b));
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static int
|
|
154
|
+
rb_dit_cb_push(dit_value_t a, dit_value_t b, void * user)
|
|
155
|
+
{
|
|
156
|
+
rb_ary_push((VALUE) user, rb_dit_interval(a, b));
|
|
157
|
+
return 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Traversals must restore `tree->traversing` even if the block raises or
|
|
161
|
+
* breaks, hence the `rb_ensure` dance */
|
|
162
|
+
typedef struct
|
|
163
|
+
{
|
|
164
|
+
dit_t * tree;
|
|
165
|
+
dit_value_t a, b;
|
|
166
|
+
dit_cb_t cb;
|
|
167
|
+
void * user;
|
|
168
|
+
|
|
169
|
+
/* whole tree instead of the [a..b[ range */
|
|
170
|
+
int all;
|
|
171
|
+
|
|
172
|
+
/* value of `tree->traversing` before the traversal started */
|
|
173
|
+
int traversing;
|
|
174
|
+
} rb_dit_traversal_t;
|
|
175
|
+
|
|
176
|
+
static VALUE
|
|
177
|
+
rb_dit_traverse_body(VALUE arg)
|
|
178
|
+
{
|
|
179
|
+
rb_dit_traversal_t * t = (rb_dit_traversal_t *) arg;
|
|
180
|
+
|
|
181
|
+
if (t->all)
|
|
182
|
+
dit_each(t->tree, t->cb, t->user);
|
|
183
|
+
else
|
|
184
|
+
dit_intersect(t->tree, t->a, t->b, t->cb, t->user);
|
|
185
|
+
|
|
186
|
+
return Qnil;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
static VALUE
|
|
190
|
+
rb_dit_traverse_ensure(VALUE arg)
|
|
191
|
+
{
|
|
192
|
+
rb_dit_traversal_t * t = (rb_dit_traversal_t *) arg;
|
|
193
|
+
t->tree->traversing = t->traversing;
|
|
194
|
+
return Qnil;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
static void
|
|
198
|
+
rb_dit_traverse(dit_t * tree, int all, dit_value_t a, dit_value_t b, dit_cb_t cb, void * user)
|
|
199
|
+
{
|
|
200
|
+
rb_dit_traversal_t t;
|
|
201
|
+
t.tree = tree;
|
|
202
|
+
t.a = a;
|
|
203
|
+
t.b = b;
|
|
204
|
+
t.cb = cb;
|
|
205
|
+
t.user = user;
|
|
206
|
+
t.all = all;
|
|
207
|
+
t.traversing = tree->traversing;
|
|
208
|
+
|
|
209
|
+
rb_ensure(rb_dit_traverse_body, (VALUE) &t, rb_dit_traverse_ensure, (VALUE) &t);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/////////////
|
|
213
|
+
// METHODS //
|
|
214
|
+
/////////////
|
|
215
|
+
|
|
216
|
+
/*
|
|
217
|
+
* call-seq:
|
|
218
|
+
* DisjointIntervalTree.new -> tree
|
|
219
|
+
* DisjointIntervalTree.new([[a, b], [c, d]]) -> tree
|
|
220
|
+
*
|
|
221
|
+
* Create a tree, optionally filled with the given intervals.
|
|
222
|
+
*/
|
|
223
|
+
static VALUE
|
|
224
|
+
rb_dit_initialize(int argc, VALUE * argv, VALUE self)
|
|
225
|
+
{
|
|
226
|
+
VALUE intervals;
|
|
227
|
+
rb_scan_args(argc, argv, "01", &intervals);
|
|
228
|
+
|
|
229
|
+
if (!NIL_P(intervals))
|
|
230
|
+
{
|
|
231
|
+
intervals = rb_check_array_type(intervals);
|
|
232
|
+
if (NIL_P(intervals))
|
|
233
|
+
rb_raise(rb_eTypeError, "expected an array of [a, b] intervals");
|
|
234
|
+
|
|
235
|
+
for (long i = 0 ; i < RARRAY_LEN(intervals) ; ++i)
|
|
236
|
+
{
|
|
237
|
+
VALUE interval = rb_check_array_type(rb_ary_entry(intervals, i));
|
|
238
|
+
if (NIL_P(interval) || RARRAY_LEN(interval) != 2)
|
|
239
|
+
rb_raise(rb_eArgError, "expected an [a, b] interval at index %ld", i);
|
|
240
|
+
rb_funcall(self, rb_intern("insert"), 2,
|
|
241
|
+
rb_ary_entry(interval, 0), rb_ary_entry(interval, 1));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return self;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* Insert `[a..b[`, returns `DIT_OK`, or raises unless `soft` is set */
|
|
249
|
+
static dit_status_t
|
|
250
|
+
rb_dit_do_insert(VALUE self, VALUE va, VALUE vb, int soft)
|
|
251
|
+
{
|
|
252
|
+
dit_t * tree = rb_dit_get_mutable(self);
|
|
253
|
+
|
|
254
|
+
const dit_value_t a = rb_dit_value(va);
|
|
255
|
+
const dit_value_t b = rb_dit_value(vb);
|
|
256
|
+
|
|
257
|
+
const dit_status_t status = dit_insert(tree, a, b);
|
|
258
|
+
|
|
259
|
+
switch (status)
|
|
260
|
+
{
|
|
261
|
+
case DIT_OK:
|
|
262
|
+
break ;
|
|
263
|
+
|
|
264
|
+
case DIT_EMPTY:
|
|
265
|
+
rb_raise(rb_eArgError,
|
|
266
|
+
"empty interval [%"PRIu64"..%"PRIu64"[, expected a < b", a, b);
|
|
267
|
+
|
|
268
|
+
case DIT_OVERLAP:
|
|
269
|
+
if (!soft)
|
|
270
|
+
{
|
|
271
|
+
const dit_node_t * other = dit_intersecting(tree, a, b);
|
|
272
|
+
rb_raise(eOverlapError,
|
|
273
|
+
"[%"PRIu64"..%"PRIu64"[ overlaps [%"PRIu64"..%"PRIu64"[",
|
|
274
|
+
a, b, other ? other->a : 0, other ? other->b : 0);
|
|
275
|
+
}
|
|
276
|
+
break ;
|
|
277
|
+
|
|
278
|
+
case DIT_NOMEM:
|
|
279
|
+
rb_memerror();
|
|
280
|
+
|
|
281
|
+
default:
|
|
282
|
+
rb_raise(eError, "unexpected insertion status %d", (int) status);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return status;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/*
|
|
289
|
+
* call-seq:
|
|
290
|
+
* tree.insert(a, b) -> self
|
|
291
|
+
*
|
|
292
|
+
* Insert the half-open interval `[a..b[`.
|
|
293
|
+
*
|
|
294
|
+
* It is a usage contract that `[a..b[` must not overlap an already inserted
|
|
295
|
+
* interval: an OverlapError is raised - and the tree is left unchanged - if
|
|
296
|
+
* it does. Adjacent intervals such as `[0..10[` and `[10..20[` do not
|
|
297
|
+
* overlap.
|
|
298
|
+
*/
|
|
299
|
+
static VALUE
|
|
300
|
+
rb_dit_insert(VALUE self, VALUE va, VALUE vb)
|
|
301
|
+
{
|
|
302
|
+
rb_dit_do_insert(self, va, vb, 0);
|
|
303
|
+
return self;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/*
|
|
307
|
+
* call-seq:
|
|
308
|
+
* tree.insert?(a, b) -> true or false
|
|
309
|
+
*
|
|
310
|
+
* Same as #insert, but returns false instead of raising when `[a..b[`
|
|
311
|
+
* overlaps an already inserted interval.
|
|
312
|
+
*/
|
|
313
|
+
static VALUE
|
|
314
|
+
rb_dit_insert_p(VALUE self, VALUE va, VALUE vb)
|
|
315
|
+
{
|
|
316
|
+
return (rb_dit_do_insert(self, va, vb, 1) == DIT_OK) ? Qtrue : Qfalse;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/*
|
|
320
|
+
* call-seq:
|
|
321
|
+
* tree.intersect(a, b) { |x, y| ... } -> self
|
|
322
|
+
* tree.intersect(a, b) -> array
|
|
323
|
+
*
|
|
324
|
+
* Yield every stored interval intersecting `[a..b[`, in increasing order.
|
|
325
|
+
* Without a block, return them as an array of `[x, y]` pairs.
|
|
326
|
+
*
|
|
327
|
+
* The tree must not be modified from within the block.
|
|
328
|
+
*/
|
|
329
|
+
static VALUE
|
|
330
|
+
rb_dit_intersect(VALUE self, VALUE va, VALUE vb)
|
|
331
|
+
{
|
|
332
|
+
dit_t * tree = rb_dit_get(self);
|
|
333
|
+
|
|
334
|
+
const dit_value_t a = rb_dit_value(va);
|
|
335
|
+
const dit_value_t b = rb_dit_value(vb);
|
|
336
|
+
|
|
337
|
+
if (rb_block_given_p())
|
|
338
|
+
{
|
|
339
|
+
rb_dit_traverse(tree, 0, a, b, rb_dit_cb_yield, NULL);
|
|
340
|
+
return self;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
VALUE ary = rb_ary_new();
|
|
344
|
+
rb_dit_traverse(tree, 0, a, b, rb_dit_cb_push, (void *) ary);
|
|
345
|
+
return ary;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/*
|
|
349
|
+
* call-seq:
|
|
350
|
+
* tree.intersect?(a, b) -> true or false
|
|
351
|
+
*
|
|
352
|
+
* Whether at least one stored interval intersect `[a..b[`. O(log n).
|
|
353
|
+
*/
|
|
354
|
+
static VALUE
|
|
355
|
+
rb_dit_intersect_p(VALUE self, VALUE va, VALUE vb)
|
|
356
|
+
{
|
|
357
|
+
const dit_t * tree = rb_dit_get(self);
|
|
358
|
+
return dit_intersect_p(tree, rb_dit_value(va), rb_dit_value(vb)) ? Qtrue : Qfalse;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/*
|
|
362
|
+
* call-seq:
|
|
363
|
+
* tree.remove(a, b) -> integer
|
|
364
|
+
* tree.remove(a, b) { |x, y| ... } -> integer
|
|
365
|
+
*
|
|
366
|
+
* Remove every stored interval intersecting `[a..b[` and return how many were
|
|
367
|
+
* removed. If a block is given, it is called with each removed interval once
|
|
368
|
+
* the removal is done.
|
|
369
|
+
*
|
|
370
|
+
* Intervals are removed as a whole: an interval only partially covered by
|
|
371
|
+
* `[a..b[` is removed entirely, never split.
|
|
372
|
+
*/
|
|
373
|
+
static VALUE
|
|
374
|
+
rb_dit_remove(VALUE self, VALUE va, VALUE vb)
|
|
375
|
+
{
|
|
376
|
+
dit_t * tree = rb_dit_get_mutable(self);
|
|
377
|
+
|
|
378
|
+
const dit_value_t a = rb_dit_value(va);
|
|
379
|
+
const dit_value_t b = rb_dit_value(vb);
|
|
380
|
+
|
|
381
|
+
/* snapshot the intervals first, so that the block cannot observe - nor
|
|
382
|
+
* corrupt - a tree being modified */
|
|
383
|
+
VALUE removed = Qnil;
|
|
384
|
+
if (rb_block_given_p())
|
|
385
|
+
{
|
|
386
|
+
removed = rb_ary_new();
|
|
387
|
+
rb_dit_traverse(tree, 0, a, b, rb_dit_cb_push, (void *) removed);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const size_t n = dit_remove(tree, a, b);
|
|
391
|
+
|
|
392
|
+
if (!NIL_P(removed))
|
|
393
|
+
{
|
|
394
|
+
RUBY_ASSERT((size_t) RARRAY_LEN(removed) == n);
|
|
395
|
+
for (long i = 0 ; i < RARRAY_LEN(removed) ; ++i)
|
|
396
|
+
{
|
|
397
|
+
VALUE interval = rb_ary_entry(removed, i);
|
|
398
|
+
rb_yield_values(2, rb_ary_entry(interval, 0), rb_ary_entry(interval, 1));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return ULL2NUM((unsigned long long) n);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
static VALUE
|
|
406
|
+
rb_dit_enum_size(VALUE self, VALUE args, VALUE eobj)
|
|
407
|
+
{
|
|
408
|
+
(void) args;
|
|
409
|
+
(void) eobj;
|
|
410
|
+
return rb_dit_size(self);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/*
|
|
414
|
+
* call-seq:
|
|
415
|
+
* tree.each { |a, b| ... } -> self
|
|
416
|
+
* tree.each -> enumerator
|
|
417
|
+
*
|
|
418
|
+
* Yield every stored interval, in increasing order.
|
|
419
|
+
*/
|
|
420
|
+
static VALUE
|
|
421
|
+
rb_dit_each(VALUE self)
|
|
422
|
+
{
|
|
423
|
+
dit_t * tree = rb_dit_get(self);
|
|
424
|
+
|
|
425
|
+
RETURN_SIZED_ENUMERATOR(self, 0, 0, rb_dit_enum_size);
|
|
426
|
+
|
|
427
|
+
rb_dit_traverse(tree, 1, 0, 0, rb_dit_cb_yield, NULL);
|
|
428
|
+
|
|
429
|
+
return self;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/*
|
|
433
|
+
* call-seq:
|
|
434
|
+
* tree.to_a -> array
|
|
435
|
+
*
|
|
436
|
+
* Every stored interval, in increasing order, as an array of `[a, b]` pairs.
|
|
437
|
+
*/
|
|
438
|
+
static VALUE
|
|
439
|
+
rb_dit_to_a(VALUE self)
|
|
440
|
+
{
|
|
441
|
+
dit_t * tree = rb_dit_get(self);
|
|
442
|
+
VALUE ary = rb_ary_new_capa((long) dit_size(tree));
|
|
443
|
+
rb_dit_traverse(tree, 1, 0, 0, rb_dit_cb_push, (void *) ary);
|
|
444
|
+
return ary;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/*
|
|
448
|
+
* call-seq:
|
|
449
|
+
* tree.at(x) -> [a, b] or nil
|
|
450
|
+
*
|
|
451
|
+
* The stored interval containing the point `x`, or nil. O(log n).
|
|
452
|
+
*/
|
|
453
|
+
static VALUE
|
|
454
|
+
rb_dit_at(VALUE self, VALUE vx)
|
|
455
|
+
{
|
|
456
|
+
const dit_t * tree = rb_dit_get(self);
|
|
457
|
+
const dit_node_t * node = dit_at(tree, rb_dit_value(vx));
|
|
458
|
+
return node ? rb_dit_interval(node->a, node->b) : Qnil;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/*
|
|
462
|
+
* call-seq:
|
|
463
|
+
* tree.cover?(x) -> true or false
|
|
464
|
+
*
|
|
465
|
+
* Whether the point `x` is covered by a stored interval. O(log n).
|
|
466
|
+
*/
|
|
467
|
+
static VALUE
|
|
468
|
+
rb_dit_cover_p(VALUE self, VALUE vx)
|
|
469
|
+
{
|
|
470
|
+
const dit_t * tree = rb_dit_get(self);
|
|
471
|
+
return dit_at(tree, rb_dit_value(vx)) ? Qtrue : Qfalse;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/*
|
|
475
|
+
* call-seq:
|
|
476
|
+
* tree.hull -> [a, b] or nil
|
|
477
|
+
*
|
|
478
|
+
* The smallest interval including every stored interval, or nil when the tree
|
|
479
|
+
* is empty.
|
|
480
|
+
*
|
|
481
|
+
* This is the hull augment of the root node, so it is O(1).
|
|
482
|
+
*/
|
|
483
|
+
static VALUE
|
|
484
|
+
rb_dit_hull(VALUE self)
|
|
485
|
+
{
|
|
486
|
+
const dit_t * tree = rb_dit_get(self);
|
|
487
|
+
|
|
488
|
+
dit_value_t a, b;
|
|
489
|
+
if (!dit_hull(tree, &a, &b))
|
|
490
|
+
return Qnil;
|
|
491
|
+
|
|
492
|
+
return rb_dit_interval(a, b);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/*
|
|
496
|
+
* call-seq:
|
|
497
|
+
* tree.size -> integer
|
|
498
|
+
*
|
|
499
|
+
* The number of stored intervals.
|
|
500
|
+
*/
|
|
501
|
+
static VALUE
|
|
502
|
+
rb_dit_size(VALUE self)
|
|
503
|
+
{
|
|
504
|
+
const dit_t * tree = rb_dit_get(self);
|
|
505
|
+
return ULL2NUM((unsigned long long) dit_size(tree));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/*
|
|
509
|
+
* call-seq:
|
|
510
|
+
* tree.empty? -> true or false
|
|
511
|
+
*/
|
|
512
|
+
static VALUE
|
|
513
|
+
rb_dit_empty_p(VALUE self)
|
|
514
|
+
{
|
|
515
|
+
const dit_t * tree = rb_dit_get(self);
|
|
516
|
+
return dit_empty(tree) ? Qtrue : Qfalse;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/*
|
|
520
|
+
* call-seq:
|
|
521
|
+
* tree.height -> integer
|
|
522
|
+
*
|
|
523
|
+
* The height of the underlying AVL tree, mostly useful for testing.
|
|
524
|
+
*/
|
|
525
|
+
static VALUE
|
|
526
|
+
rb_dit_height(VALUE self)
|
|
527
|
+
{
|
|
528
|
+
const dit_t * tree = rb_dit_get(self);
|
|
529
|
+
return INT2NUM(dit_height(tree));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/*
|
|
533
|
+
* call-seq:
|
|
534
|
+
* tree.clear -> self
|
|
535
|
+
*
|
|
536
|
+
* Remove every stored interval.
|
|
537
|
+
*/
|
|
538
|
+
static VALUE
|
|
539
|
+
rb_dit_clear(VALUE self)
|
|
540
|
+
{
|
|
541
|
+
dit_t * tree = rb_dit_get_mutable(self);
|
|
542
|
+
dit_clear(tree);
|
|
543
|
+
return self;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/*
|
|
547
|
+
* call-seq:
|
|
548
|
+
* tree.check! -> self
|
|
549
|
+
*
|
|
550
|
+
* Verify every structural invariant of the underlying tree, and raise
|
|
551
|
+
* CorruptedError if one is broken. Mostly useful for testing, it is O(n).
|
|
552
|
+
*/
|
|
553
|
+
static VALUE
|
|
554
|
+
rb_dit_check(VALUE self)
|
|
555
|
+
{
|
|
556
|
+
const dit_t * tree = rb_dit_get(self);
|
|
557
|
+
|
|
558
|
+
char err[512];
|
|
559
|
+
if (dit_check(tree, err, sizeof(err)))
|
|
560
|
+
rb_raise(eCorruptedError, "%s", err);
|
|
561
|
+
|
|
562
|
+
return self;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
static VALUE
|
|
566
|
+
rb_dit_inspect(VALUE self)
|
|
567
|
+
{
|
|
568
|
+
const dit_t * tree = rb_dit_get(self);
|
|
569
|
+
return rb_sprintf("#<%"PRIsVALUE" size=%lu height=%d>",
|
|
570
|
+
rb_obj_class(self), (unsigned long) dit_size(tree), dit_height(tree));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/*
|
|
574
|
+
* call-seq:
|
|
575
|
+
* tree.initialize_copy(other) -> self
|
|
576
|
+
*
|
|
577
|
+
* Called by #dup and #clone.
|
|
578
|
+
*/
|
|
579
|
+
static VALUE
|
|
580
|
+
rb_dit_initialize_copy(VALUE self, VALUE other)
|
|
581
|
+
{
|
|
582
|
+
dit_t * tree = rb_dit_get_mutable(self);
|
|
583
|
+
dit_t * src = rb_dit_get(other);
|
|
584
|
+
|
|
585
|
+
if (tree == src)
|
|
586
|
+
return self;
|
|
587
|
+
|
|
588
|
+
dit_clear(tree);
|
|
589
|
+
|
|
590
|
+
/* in-order insertions would degenerate into a rotation at every step, but
|
|
591
|
+
* the AVL rebalancing keeps it O(n.log n) overall */
|
|
592
|
+
VALUE intervals = rb_ary_new_capa((long) dit_size(src));
|
|
593
|
+
rb_dit_traverse(src, 1, 0, 0, rb_dit_cb_push, (void *) intervals);
|
|
594
|
+
|
|
595
|
+
for (long i = 0 ; i < RARRAY_LEN(intervals) ; ++i)
|
|
596
|
+
{
|
|
597
|
+
VALUE interval = rb_ary_entry(intervals, i);
|
|
598
|
+
const dit_value_t a = rb_dit_value(rb_ary_entry(interval, 0));
|
|
599
|
+
const dit_value_t b = rb_dit_value(rb_ary_entry(interval, 1));
|
|
600
|
+
if (dit_insert(tree, a, b) != DIT_OK)
|
|
601
|
+
rb_raise(eError, "could not copy interval [%"PRIu64"..%"PRIu64"[", a, b);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return self;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
void
|
|
608
|
+
Init_disjoint_interval_tree(void)
|
|
609
|
+
{
|
|
610
|
+
cTree = rb_define_class("DisjointIntervalTree", rb_cObject);
|
|
611
|
+
rb_include_module(cTree, rb_mEnumerable);
|
|
612
|
+
|
|
613
|
+
eError = rb_define_class_under(cTree, "Error", rb_eStandardError);
|
|
614
|
+
eOverlapError = rb_define_class_under(cTree, "OverlapError", eError);
|
|
615
|
+
eCorruptedError = rb_define_class_under(cTree, "CorruptedError", eError);
|
|
616
|
+
|
|
617
|
+
/* the largest representable bound, exclusive: intervals live in
|
|
618
|
+
* [0 .. DisjointIntervalTree::MAX[ */
|
|
619
|
+
rb_define_const(cTree, "MAX", ULL2NUM((unsigned long long) DIT_VALUE_MAX));
|
|
620
|
+
|
|
621
|
+
rb_define_alloc_func(cTree, rb_dit_alloc);
|
|
622
|
+
|
|
623
|
+
rb_define_method(cTree, "initialize", rb_dit_initialize, -1);
|
|
624
|
+
rb_define_method(cTree, "initialize_copy", rb_dit_initialize_copy, 1);
|
|
625
|
+
|
|
626
|
+
rb_define_method(cTree, "insert", rb_dit_insert, 2);
|
|
627
|
+
rb_define_method(cTree, "insert?", rb_dit_insert_p, 2);
|
|
628
|
+
rb_define_method(cTree, "intersect", rb_dit_intersect, 2);
|
|
629
|
+
rb_define_method(cTree, "intersect?", rb_dit_intersect_p, 2);
|
|
630
|
+
rb_define_method(cTree, "remove", rb_dit_remove, 2);
|
|
631
|
+
rb_define_method(cTree, "each", rb_dit_each, 0);
|
|
632
|
+
rb_define_method(cTree, "to_a", rb_dit_to_a, 0);
|
|
633
|
+
rb_define_method(cTree, "at", rb_dit_at, 1);
|
|
634
|
+
rb_define_method(cTree, "cover?", rb_dit_cover_p, 1);
|
|
635
|
+
rb_define_method(cTree, "hull", rb_dit_hull, 0);
|
|
636
|
+
rb_define_method(cTree, "size", rb_dit_size, 0);
|
|
637
|
+
rb_define_method(cTree, "empty?", rb_dit_empty_p, 0);
|
|
638
|
+
rb_define_method(cTree, "height", rb_dit_height, 0);
|
|
639
|
+
rb_define_method(cTree, "clear", rb_dit_clear, 0);
|
|
640
|
+
rb_define_method(cTree, "check!", rb_dit_check, 0);
|
|
641
|
+
rb_define_method(cTree, "inspect", rb_dit_inspect, 0);
|
|
642
|
+
|
|
643
|
+
rb_define_alias(cTree, "each_intersecting", "intersect");
|
|
644
|
+
rb_define_alias(cTree, "overlaps?", "intersect?");
|
|
645
|
+
rb_define_alias(cTree, "length", "size");
|
|
646
|
+
rb_define_alias(cTree, "entries", "to_a");
|
|
647
|
+
rb_define_alias(cTree, "to_s", "inspect");
|
|
648
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mkmf'
|
|
4
|
+
|
|
5
|
+
# `--enable-paranoid` runs a full O(n) coherency check of the tree after every
|
|
6
|
+
# mutation. Only useful to debug the extension itself.
|
|
7
|
+
if enable_config('paranoid', false)
|
|
8
|
+
$defs << '-DDIT_PARANOID=1'
|
|
9
|
+
warn 'disjoint_interval_tree: building with paranoid coherency checks'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# `--disable-assertions` compiles out every internal consistency assertion.
|
|
13
|
+
$defs << '-DNDEBUG' unless enable_config('assertions', true)
|
|
14
|
+
|
|
15
|
+
append_cflags(['-std=gnu11', '-Wall', '-Wextra'])
|
|
16
|
+
|
|
17
|
+
create_makefile('disjoint_interval_tree/disjoint_interval_tree')
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2025 INRIA
|
|
5
|
+
#
|
|
6
|
+
# Contributors :
|
|
7
|
+
# Romain PEREIRA, romain.pereira@inria.fr + rpereira@anl.gov
|
|
8
|
+
#
|
|
9
|
+
# This software is governed by the CeCILL-C license under French law and
|
|
10
|
+
# abiding by the rules of distribution of free software. You can use,
|
|
11
|
+
# modify and/ or redistribute the software under the terms of the CeCILL-C
|
|
12
|
+
# license as circulated by CEA, CNRS and INRIA at the following URL
|
|
13
|
+
# "http://www.cecill.info".
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
require 'disjoint_interval_tree/disjoint_interval_tree'
|
|
17
|
+
require 'disjoint_interval_tree/version'
|
|
18
|
+
|
|
19
|
+
# A set of pairwise disjoint half-open intervals `[a..b[`, backed by an
|
|
20
|
+
# augmented AVL tree written in C.
|
|
21
|
+
#
|
|
22
|
+
# Bounds are unsigned 64 bits integers, so intervals live in
|
|
23
|
+
# `[0 .. DisjointIntervalTree::MAX[`.
|
|
24
|
+
#
|
|
25
|
+
# tree = DisjointIntervalTree.new
|
|
26
|
+
# tree.insert(0, 10)
|
|
27
|
+
# tree.insert(20, 30)
|
|
28
|
+
#
|
|
29
|
+
# tree.intersect(5, 25) { |a, b| puts "[#{a}..#{b}[" }
|
|
30
|
+
# # => [0..10[
|
|
31
|
+
# # => [20..30[
|
|
32
|
+
#
|
|
33
|
+
# tree.remove(5, 25) # => 2
|
|
34
|
+
# tree.to_a # => []
|
|
35
|
+
#
|
|
36
|
+
# Inserting an interval overlapping an already inserted one is a usage
|
|
37
|
+
# contract violation and raises DisjointIntervalTree::OverlapError.
|
|
38
|
+
class DisjointIntervalTree
|
|
39
|
+
# Two trees are equal when they hold the same intervals.
|
|
40
|
+
def ==(other)
|
|
41
|
+
other.is_a?(DisjointIntervalTree) && to_a == other.to_a
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
alias eql? ==
|
|
45
|
+
|
|
46
|
+
# The total length covered by the stored intervals.
|
|
47
|
+
def coverage
|
|
48
|
+
sum = 0
|
|
49
|
+
each { |a, b| sum += b - a }
|
|
50
|
+
sum
|
|
51
|
+
end
|
|
52
|
+
end
|