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.
@@ -0,0 +1,649 @@
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 'minitest/autorun'
17
+ require 'disjoint_interval_tree'
18
+
19
+ # A naive reference implementation, used to cross-check the tree.
20
+ class IntervalModel
21
+ attr_reader :intervals
22
+
23
+ def initialize
24
+ @intervals = []
25
+ end
26
+
27
+ def intersect(a, b)
28
+ return [] if a >= b
29
+
30
+ @intervals.select { |x, y| a < y && x < b }
31
+ end
32
+
33
+ def insert(a, b)
34
+ return false if a >= b || !intersect(a, b).empty?
35
+
36
+ @intervals << [a, b]
37
+ @intervals.sort!
38
+ true
39
+ end
40
+
41
+ def remove(a, b)
42
+ hit = intersect(a, b)
43
+ @intervals -= hit
44
+ hit.size
45
+ end
46
+
47
+ def size
48
+ @intervals.size
49
+ end
50
+ end
51
+
52
+ class TestDisjointIntervalTree < Minitest::Test
53
+ def setup
54
+ @tree = DisjointIntervalTree.new
55
+ end
56
+
57
+ def teardown
58
+ @tree.check!
59
+ end
60
+
61
+ # a tree holding [0..10[ [20..30[ [40..50[ [60..70[
62
+ def spaced_tree
63
+ tree = DisjointIntervalTree.new
64
+ 4.times { |i| tree.insert(20 * i, 20 * i + 10) }
65
+ tree
66
+ end
67
+
68
+ ##########
69
+ # BASICS #
70
+ ##########
71
+
72
+ def test_empty_tree
73
+ assert_empty @tree
74
+ assert_equal 0, @tree.size
75
+ assert_equal 0, @tree.length
76
+ assert_equal 0, @tree.height
77
+ assert_equal [], @tree.to_a
78
+ assert_nil @tree.at(0)
79
+ assert_nil @tree.hull
80
+ refute @tree.cover?(0)
81
+ refute @tree.intersect?(0, 100)
82
+ assert_equal [], @tree.intersect(0, 100)
83
+ assert_equal 0, @tree.remove(0, 100)
84
+ end
85
+
86
+ def test_insert_returns_self
87
+ assert_same @tree, @tree.insert(0, 10)
88
+ end
89
+
90
+ def test_insert_single
91
+ @tree.insert(10, 20)
92
+
93
+ refute_empty @tree
94
+ assert_equal 1, @tree.size
95
+ assert_equal 1, @tree.height
96
+ assert_equal [[10, 20]], @tree.to_a
97
+ assert_equal [10, 20], @tree.hull
98
+ end
99
+
100
+ def test_intervals_are_half_open
101
+ @tree.insert(10, 20)
102
+
103
+ assert_nil @tree.at(9)
104
+ assert_equal [10, 20], @tree.at(10)
105
+ assert_equal [10, 20], @tree.at(19)
106
+ assert_nil @tree.at(20)
107
+
108
+ refute @tree.cover?(9)
109
+ assert @tree.cover?(10)
110
+ assert @tree.cover?(19)
111
+ refute @tree.cover?(20)
112
+ end
113
+
114
+ def test_adjacent_intervals_do_not_overlap
115
+ @tree.insert(0, 10)
116
+ @tree.insert(10, 20)
117
+ @tree.insert(20, 30)
118
+
119
+ assert_equal 3, @tree.size
120
+ assert_equal [[0, 10], [10, 20], [20, 30]], @tree.to_a
121
+ assert_equal [0, 30], @tree.hull
122
+ end
123
+
124
+ def test_intervals_are_ordered
125
+ [50, 10, 90, 30, 70, 0, 20].each { |a| @tree.insert(a, a + 5) }
126
+
127
+ assert_equal [[0, 5], [10, 15], [20, 25], [30, 35], [50, 55], [70, 75], [90, 95]],
128
+ @tree.to_a
129
+ assert_equal @tree.to_a, @tree.to_a.sort
130
+ end
131
+
132
+ def test_coverage
133
+ @tree.insert(0, 10)
134
+ @tree.insert(100, 130)
135
+
136
+ assert_equal 40, @tree.coverage
137
+ end
138
+
139
+ def test_inspect
140
+ @tree.insert(0, 10)
141
+
142
+ assert_equal '#<DisjointIntervalTree size=1 height=1>', @tree.inspect
143
+ assert_equal @tree.inspect, @tree.to_s
144
+ end
145
+
146
+ ############
147
+ # CONTRACT #
148
+ ############
149
+
150
+ def test_insert_overlapping_raises
151
+ @tree.insert(10, 20)
152
+ @tree.insert(30, 40)
153
+
154
+ overlapping = [
155
+ [10, 20], # identical
156
+ [12, 18], # strictly included
157
+ [5, 25], # strictly including
158
+ [5, 11], # overlapping on the left
159
+ [19, 25], # overlapping on the right
160
+ [0, 35] # spanning both
161
+ ]
162
+
163
+ overlapping.each do |a, b|
164
+ assert_raises(DisjointIntervalTree::OverlapError) { @tree.insert(a, b) }
165
+ end
166
+
167
+ # the tree was left untouched
168
+ assert_equal [[10, 20], [30, 40]], @tree.to_a
169
+ end
170
+
171
+ def test_overlap_error_is_a_standard_error
172
+ @tree.insert(10, 20)
173
+
174
+ error = assert_raises(DisjointIntervalTree::OverlapError) { @tree.insert(15, 25) }
175
+
176
+ assert_kind_of DisjointIntervalTree::Error, error
177
+ assert_kind_of StandardError, error
178
+ assert_match(/\[15\.\.25\[ overlaps \[10\.\.20\[/, error.message)
179
+ end
180
+
181
+ def test_insert_predicate_does_not_raise
182
+ assert @tree.insert?(10, 20)
183
+ refute @tree.insert?(15, 25)
184
+ assert @tree.insert?(20, 25)
185
+ assert_equal [[10, 20], [20, 25]], @tree.to_a
186
+ end
187
+
188
+ def test_insert_empty_interval_raises
189
+ assert_raises(ArgumentError) { @tree.insert(10, 10) }
190
+ assert_raises(ArgumentError) { @tree.insert(20, 10) }
191
+ assert_raises(ArgumentError) { @tree.insert?(10, 10) }
192
+ assert_empty @tree
193
+ end
194
+
195
+ def test_insert_invalid_bounds_raise
196
+ # negative bounds must not silently wrap around
197
+ assert_raises(RangeError) { @tree.insert(-1, 10) }
198
+ assert_raises(RangeError) { @tree.insert(-(2**63), 10) }
199
+ assert_raises(RangeError) { @tree.insert(-(2**70), 10) }
200
+ assert_raises(RangeError) { @tree.insert(0, -1) }
201
+
202
+ # nor must out of range ones
203
+ assert_raises(RangeError) { @tree.insert(0, DisjointIntervalTree::MAX + 1) }
204
+ assert_raises(RangeError) { @tree.insert(2**64, 2**65) }
205
+
206
+ # only integers are accepted, floats are not silently truncated
207
+ assert_raises(TypeError) { @tree.insert(1.5, 10.5) }
208
+ assert_raises(TypeError) { @tree.insert('0', 10) }
209
+ assert_raises(TypeError) { @tree.insert(nil, 10) }
210
+
211
+ assert_empty @tree
212
+ end
213
+
214
+ def test_invalid_bounds_raise_everywhere
215
+ assert_raises(RangeError) { @tree.remove(-1, 10) }
216
+ assert_raises(RangeError) { @tree.intersect(-1, 10) }
217
+ assert_raises(RangeError) { @tree.intersect?(-1, 10) }
218
+ assert_raises(RangeError) { @tree.at(-1) }
219
+ assert_raises(RangeError) { @tree.cover?(-1) }
220
+ assert_raises(TypeError) { @tree.at('nope') }
221
+ end
222
+
223
+ def test_large_bounds_are_supported
224
+ @tree.insert(2**63, 2**63 + 10)
225
+
226
+ assert_equal [[2**63, 2**63 + 10]], @tree.to_a
227
+ assert_equal [2**63, 2**63 + 10], @tree.at(2**63 + 5)
228
+ end
229
+
230
+ ##############
231
+ # INTERSECTS #
232
+ ##############
233
+
234
+ def test_intersect_yields_matching_intervals
235
+ tree = spaced_tree
236
+ seen = []
237
+ result = tree.intersect(5, 45) { |a, b| seen << [a, b] }
238
+
239
+ assert_same tree, result
240
+ assert_equal [[0, 10], [20, 30], [40, 50]], seen
241
+ end
242
+
243
+ def test_intersect_without_block_returns_an_array
244
+ assert_equal [[0, 10], [20, 30], [40, 50]], spaced_tree.intersect(5, 45)
245
+ end
246
+
247
+ def test_intersect_reports_nothing_when_querying_a_hole
248
+ assert_equal [], spaced_tree.intersect(12, 18)
249
+ end
250
+
251
+ def test_intersect_ignores_adjacency
252
+ tree = spaced_tree
253
+
254
+ assert_equal [], tree.intersect(10, 20)
255
+ assert_equal [[0, 10]], tree.intersect(9, 20)
256
+ assert_equal [[20, 30]], tree.intersect(10, 21)
257
+ end
258
+
259
+ def test_intersect_on_an_empty_query
260
+ assert_equal [], spaced_tree.intersect(5, 5)
261
+ assert_equal [], spaced_tree.intersect(45, 5)
262
+ end
263
+
264
+ def test_intersect_out_of_range
265
+ assert_equal [], spaced_tree.intersect(1000, 2000)
266
+ end
267
+
268
+ def test_intersect_everything
269
+ assert_equal 4, spaced_tree.intersect(0, 1000).size
270
+ end
271
+
272
+ def test_intersect_predicate
273
+ tree = spaced_tree
274
+
275
+ refute tree.intersect?(12, 18)
276
+ refute tree.intersect?(10, 20)
277
+ assert tree.intersect?(9, 20)
278
+ assert tree.intersect?(0, 1000)
279
+ assert tree.overlaps?(0, 1000)
280
+ end
281
+
282
+ def test_each_intersecting_is_an_alias
283
+ assert_equal spaced_tree.intersect(5, 45), spaced_tree.each_intersecting(5, 45)
284
+ end
285
+
286
+ def test_intersect_can_be_broken_out_of
287
+ tree = spaced_tree
288
+ seen = []
289
+
290
+ result = tree.intersect(0, 1000) do |a, b|
291
+ seen << [a, b]
292
+ break :stopped if seen.size == 2
293
+ end
294
+
295
+ assert_equal :stopped, result
296
+ assert_equal [[0, 10], [20, 30]], seen
297
+
298
+ # the traversal state was properly restored
299
+ tree.insert(100, 110)
300
+ assert_equal 5, tree.size
301
+ tree.check!
302
+ end
303
+
304
+ def test_an_exception_raised_in_a_block_leaves_the_tree_usable
305
+ tree = spaced_tree
306
+
307
+ assert_raises(RuntimeError) do
308
+ tree.intersect(0, 1000) { raise 'boom' }
309
+ end
310
+
311
+ tree.insert(100, 110)
312
+ assert_equal 5, tree.size
313
+ tree.check!
314
+ end
315
+
316
+ ##############
317
+ # REENTRANCY #
318
+ ##############
319
+
320
+ def test_mutating_while_traversing_raises
321
+ tree = spaced_tree
322
+
323
+ assert_raises(DisjointIntervalTree::Error) do
324
+ tree.each { tree.insert(1000, 1010) }
325
+ end
326
+
327
+ assert_raises(DisjointIntervalTree::Error) do
328
+ tree.intersect(0, 1000) { tree.remove(0, 10) }
329
+ end
330
+
331
+ assert_raises(DisjointIntervalTree::Error) do
332
+ tree.each { tree.clear }
333
+ end
334
+
335
+ # and the tree is unharmed
336
+ assert_equal 4, tree.size
337
+ tree.check!
338
+ end
339
+
340
+ def test_mutating_a_frozen_tree_raises
341
+ tree = spaced_tree.freeze
342
+
343
+ assert_raises(FrozenError) { tree.insert(100, 110) }
344
+ assert_raises(FrozenError) { tree.insert?(100, 110) }
345
+ assert_raises(FrozenError) { tree.remove(0, 10) }
346
+ assert_raises(FrozenError) { tree.clear }
347
+
348
+ # reading is still fine
349
+ assert_equal 4, tree.size
350
+ assert_equal [[0, 10]], tree.intersect(0, 10)
351
+ tree.check!
352
+ end
353
+
354
+ def test_reading_while_traversing_is_allowed
355
+ tree = spaced_tree
356
+ sizes = []
357
+
358
+ tree.each { sizes << tree.size + tree.intersect(0, 1000).size }
359
+
360
+ assert_equal [8, 8, 8, 8], sizes
361
+ end
362
+
363
+ ##########
364
+ # REMOVE #
365
+ ##########
366
+
367
+ def test_remove_returns_the_number_of_removed_intervals
368
+ tree = spaced_tree
369
+
370
+ assert_equal 0, tree.remove(10, 20)
371
+ assert_equal 4, tree.size
372
+
373
+ assert_equal 3, tree.remove(5, 45)
374
+ assert_equal [[60, 70]], tree.to_a
375
+ end
376
+
377
+ def test_remove_does_not_split_intervals
378
+ tree = spaced_tree
379
+
380
+ assert_equal 1, tree.remove(25, 26)
381
+ assert_nil tree.at(20)
382
+ assert_nil tree.at(29)
383
+ assert_equal [[0, 10], [40, 50], [60, 70]], tree.to_a
384
+ end
385
+
386
+ def test_remove_ignores_adjacency
387
+ tree = spaced_tree
388
+
389
+ assert_equal 0, tree.remove(10, 20)
390
+ assert_equal 0, tree.remove(30, 40)
391
+ assert_equal 4, tree.size
392
+ end
393
+
394
+ def test_remove_on_an_empty_query
395
+ tree = spaced_tree
396
+
397
+ assert_equal 0, tree.remove(5, 5)
398
+ assert_equal 0, tree.remove(45, 5)
399
+ assert_equal 4, tree.size
400
+ end
401
+
402
+ def test_remove_yields_removed_intervals
403
+ tree = spaced_tree
404
+ seen = []
405
+
406
+ assert_equal 3, tree.remove(5, 45) { |a, b| seen << [a, b] }
407
+ assert_equal [[0, 10], [20, 30], [40, 50]], seen
408
+ assert_equal [[60, 70]], tree.to_a
409
+ end
410
+
411
+ def test_remove_then_reinsert
412
+ tree = spaced_tree
413
+
414
+ tree.remove(20, 30)
415
+ assert_raises(DisjointIntervalTree::OverlapError) { tree.insert(0, 10) }
416
+ tree.insert(20, 30)
417
+ assert_equal 4, tree.size
418
+ end
419
+
420
+ def test_remove_everything
421
+ tree = spaced_tree
422
+
423
+ assert_equal 4, tree.remove(0, DisjointIntervalTree::MAX)
424
+ assert_empty tree
425
+ assert_equal 0, tree.remove(0, DisjointIntervalTree::MAX)
426
+ end
427
+
428
+ def test_clear
429
+ tree = spaced_tree
430
+
431
+ assert_same tree, tree.clear
432
+ assert_empty tree
433
+ assert_equal [], tree.to_a
434
+
435
+ tree.insert(0, 1000)
436
+ assert_equal 1, tree.size
437
+ end
438
+
439
+ ##############
440
+ # ENUMERABLE #
441
+ ##############
442
+
443
+ def test_each_returns_an_enumerator_without_a_block
444
+ tree = spaced_tree
445
+ enumerator = tree.each
446
+
447
+ assert_kind_of Enumerator, enumerator
448
+ assert_equal 4, enumerator.size
449
+ assert_equal [[0, 10], [20, 30], [40, 50], [60, 70]], enumerator.to_a
450
+ end
451
+
452
+ def test_enumerable_methods
453
+ tree = spaced_tree
454
+
455
+ assert_equal [10, 10, 10, 10], tree.map { |a, b| b - a }
456
+ assert_equal [[0, 10]], tree.select { |a, _| a.zero? }
457
+ assert_equal [0, 10], tree.first
458
+ assert_equal [[0, 10], [20, 30]], tree.first(2)
459
+ assert tree.include?([20, 30])
460
+ refute tree.include?([20, 31])
461
+ assert_equal 4, tree.count
462
+ assert_equal 40, tree.sum { |a, b| b - a }
463
+ end
464
+
465
+ def test_entries_is_an_alias_of_to_a
466
+ assert_equal spaced_tree.to_a, spaced_tree.entries
467
+ end
468
+
469
+ ###############
470
+ # CONSTRUCTOR #
471
+ ###############
472
+
473
+ def test_new_with_intervals
474
+ tree = DisjointIntervalTree.new([[20, 30], [0, 10]])
475
+
476
+ assert_equal [[0, 10], [20, 30]], tree.to_a
477
+ end
478
+
479
+ def test_new_with_overlapping_intervals_raises
480
+ assert_raises(DisjointIntervalTree::OverlapError) do
481
+ DisjointIntervalTree.new([[0, 10], [5, 15]])
482
+ end
483
+ end
484
+
485
+ def test_new_with_garbage_raises
486
+ assert_raises(TypeError) { DisjointIntervalTree.new(42) }
487
+ assert_raises(ArgumentError) { DisjointIntervalTree.new([[0, 10, 20]]) }
488
+ assert_raises(ArgumentError) { DisjointIntervalTree.new([0]) }
489
+ end
490
+
491
+ def test_dup_is_a_deep_copy
492
+ tree = spaced_tree
493
+ copy = tree.dup
494
+
495
+ assert_equal tree, copy
496
+ assert_equal tree.to_a, copy.to_a
497
+
498
+ copy.insert(100, 110)
499
+ refute_equal tree, copy
500
+ assert_equal 4, tree.size
501
+ assert_equal 5, copy.size
502
+
503
+ copy.check!
504
+ tree.check!
505
+ end
506
+
507
+ def test_equality
508
+ assert_equal DisjointIntervalTree.new, DisjointIntervalTree.new
509
+ assert_equal spaced_tree, spaced_tree
510
+ refute_equal spaced_tree, DisjointIntervalTree.new
511
+ refute_equal spaced_tree, [[0, 10], [20, 30], [40, 50], [60, 70]]
512
+ end
513
+
514
+ ##################
515
+ # EXTREME VALUES #
516
+ ##################
517
+
518
+ def test_extreme_values
519
+ max = DisjointIntervalTree::MAX
520
+
521
+ @tree.insert(0, 1)
522
+ @tree.insert(max - 1, max)
523
+ @tree.insert(1, max - 1)
524
+
525
+ assert_equal [0, max], @tree.hull
526
+ assert_equal [max - 1, max], @tree.at(max - 1)
527
+
528
+ # no half-open interval can ever contain MAX
529
+ assert_nil @tree.at(max)
530
+ refute @tree.cover?(max)
531
+
532
+ assert_equal 3, @tree.intersect(0, max).size
533
+ assert_equal 3, @tree.remove(0, max)
534
+ end
535
+
536
+ ###########
537
+ # BALANCE #
538
+ ###########
539
+
540
+ def test_sorted_insertions_stay_balanced
541
+ n = 4095
542
+ n.times { |i| @tree.insert(2 * i, 2 * i + 1) }
543
+
544
+ assert_equal n, @tree.size
545
+
546
+ # 4095 nodes fit in a perfectly balanced tree of height 12, an AVL tree is
547
+ # at most ~1.44*log2(n) deep
548
+ assert_operator @tree.height, :<=, 18
549
+ end
550
+
551
+ def test_reverse_sorted_insertions_stay_balanced
552
+ n = 4095
553
+ n.downto(1) { |i| @tree.insert(2 * i, 2 * i + 1) }
554
+
555
+ assert_equal n, @tree.size
556
+ assert_operator @tree.height, :<=, 18
557
+ end
558
+
559
+ def test_sequential_removals_stay_balanced
560
+ n = 1023
561
+ n.times { |i| @tree.insert(2 * i, 2 * i + 1) }
562
+
563
+ n.times do |i|
564
+ assert_equal 1, @tree.remove(2 * i, 2 * i + 1)
565
+ assert_equal n - i - 1, @tree.size
566
+ assert_operator @tree.height, :<=, 16
567
+ end
568
+
569
+ assert_empty @tree
570
+ end
571
+
572
+ ##############
573
+ # RANDOMIZED #
574
+ ##############
575
+
576
+ def test_random_against_a_reference_model
577
+ srand(Integer(ENV.fetch('SEED', 1)))
578
+
579
+ universe = 512
580
+ model = IntervalModel.new
581
+
582
+ 2000.times do |iteration|
583
+ a = rand(universe)
584
+ b = a + 1 + rand(8)
585
+
586
+ case rand(3)
587
+ when 0
588
+ if model.intersect(a, b).empty?
589
+ @tree.insert(a, b)
590
+ model.insert(a, b)
591
+ else
592
+ assert_raises(DisjointIntervalTree::OverlapError) { @tree.insert(a, b) }
593
+ end
594
+ when 1
595
+ assert_equal model.remove(a, b), @tree.remove(a, b),
596
+ "iteration #{iteration}: remove(#{a}, #{b})"
597
+ else
598
+ assert_equal model.intersect(a, b), @tree.intersect(a, b),
599
+ "iteration #{iteration}: intersect(#{a}, #{b})"
600
+ end
601
+
602
+ assert_equal model.size, @tree.size, "iteration #{iteration}"
603
+ assert_equal model.intervals, @tree.to_a, "iteration #{iteration}"
604
+ @tree.check!
605
+ end
606
+
607
+ refute_empty @tree
608
+ end
609
+
610
+ def test_random_point_queries
611
+ srand(Integer(ENV.fetch('SEED', 1)))
612
+
613
+ model = IntervalModel.new
614
+ 200.times do
615
+ a = rand(4096)
616
+ b = a + 1 + rand(16)
617
+ model.insert(a, b) && @tree.insert(a, b)
618
+ end
619
+
620
+ @tree.check!
621
+
622
+ 2000.times do
623
+ x = rand(4096 + 32)
624
+ expected = model.intervals.find { |a, b| a <= x && x < b }
625
+
626
+ if expected
627
+ assert_equal expected, @tree.at(x), "at(#{x})"
628
+ assert @tree.cover?(x), "cover?(#{x})"
629
+ else
630
+ assert_nil @tree.at(x), "at(#{x})"
631
+ refute @tree.cover?(x), "cover?(#{x})"
632
+ end
633
+ end
634
+ end
635
+
636
+ def test_garbage_collection
637
+ 100.times do
638
+ tree = DisjointIntervalTree.new
639
+ 100.times { |i| tree.insert(10 * i, 10 * i + 5) }
640
+ tree.check!
641
+ end
642
+
643
+ GC.start
644
+ GC.compact if GC.respond_to?(:compact)
645
+
646
+ @tree.insert(0, 10)
647
+ assert_equal [[0, 10]], @tree.to_a
648
+ end
649
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: disjoint_interval_tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Romain PEREIRA
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-09-21 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ A set of pairwise disjoint half-open intervals [a..b[, backed by an
15
+ augmented AVL tree written in C. Insertion, point lookup, intersection
16
+ queries and range removal are all logarithmic. Each node caches the hull of
17
+ the subtree it roots, so intersection queries prune whole subtrees in O(1).
18
+ email:
19
+ - romain.pereira@inria.fr
20
+ - rpereira@anl.gov
21
+ executables: []
22
+ extensions:
23
+ - ext/disjoint_interval_tree/extconf.rb
24
+ extra_rdoc_files: []
25
+ files:
26
+ - CITATION.cff
27
+ - README.md
28
+ - Rakefile
29
+ - disjoint_interval_tree.gemspec
30
+ - ext/disjoint_interval_tree/dit.c
31
+ - ext/disjoint_interval_tree/dit.h
32
+ - ext/disjoint_interval_tree/dit_ruby.c
33
+ - ext/disjoint_interval_tree/extconf.rb
34
+ - lib/disjoint_interval_tree.rb
35
+ - lib/disjoint_interval_tree/version.rb
36
+ - test/c/Makefile
37
+ - test/c/test_dit.c
38
+ - test/test_disjoint_interval_tree.rb
39
+ homepage: https://github.com/anlsys/ruby-disjoint-interval-tree
40
+ licenses:
41
+ - CECILL-C
42
+ metadata:
43
+ source_code_uri: https://github.com/anlsys/ruby-disjoint-interval-tree
44
+ bug_tracker_uri: https://github.com/anlsys/ruby-disjoint-interval-tree/issues
45
+ rubygems_mfa_required: 'true'
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 2.7.0
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.5.16
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Disjoint interval tree, in C, with Ruby bindings
65
+ test_files: []