disjoint_interval_tree 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2489f3da4496b1c3abb350eb582d52a40298e61fd9132f52f3ca80e69aeda976
4
- data.tar.gz: a3a35b0752910b4ae0e5f2c5aa021bf2175b4ae3545041670ee1fb2f7fe20c96
3
+ metadata.gz: b89b66e1ed427b22939713e6e3d8cdcaf25b3ea9d5ce352a83fc785a17abde66
4
+ data.tar.gz: 4885d5e8432c76ce4b8f37c78efdc9981f4815fa9baf60f791e058480aa8ae17
5
5
  SHA512:
6
- metadata.gz: 8937708e32708573ed399b91722a9ccc1ff8c9d338f6e6655c089a996e4228b0685d653d6b4dd2729e121f384a0a622a0f9cba585475128304633bf80c1830b0
7
- data.tar.gz: af0540688e96cc9aa9eebfa5951d4b04aafc04e137b7d7113e959fef4d2621eb81d1a63f7fcbfbbd81d8716cf7fba2d3507e4fc20669265f94fbb2821a7dc163
6
+ metadata.gz: a4d8ee9fba831a1a78cd10b490c2928236c5f62897f791b86edb7d19f01d5090726eb4842b6a9466ba97637b6213fd4386d2c8401813d577491e5bd92b930f5e
7
+ data.tar.gz: 3f18cbc6d7ef893b1e41a0027a2a3770e5e1610f51a5d2e729b8517a4bea279549162d8e9873091b38387f0f083a0a451fbdd5ec608fcc813cbcd24b385bf98d
data/README.md CHANGED
@@ -29,15 +29,16 @@ time.
29
29
  require 'disjoint_interval_tree'
30
30
 
31
31
  tree = DisjointIntervalTree.new
32
- tree.insert(10, 20)
33
- tree.insert(30, 40)
32
+ tree.insert(10, 20, 'first')
33
+ tree.insert(30, 40, 'second')
34
34
 
35
- tree.intersect(15, 35) do |a, b|
36
- puts "[#{a}..#{b}["
35
+ tree.intersect(15, 35) do |a, b, obj|
36
+ puts "[#{a}..#{b}[ -> #{obj}"
37
37
  end
38
- # => [10..20[
39
- # => [30..40[
38
+ # => [10..20[ -> first
39
+ # => [30..40[ -> second
40
40
 
41
+ tree[15] # => "first"
41
42
  tree.remove(15, 35) # => 2
42
43
  tree.to_a # => []
43
44
  ```
@@ -52,6 +53,9 @@ tree.to_a # => []
52
53
  caller never inserts an interval overlapping an already inserted one.
53
54
  Breaking it raises `DisjointIntervalTree::OverlapError` and leaves the tree
54
55
  unchanged - intervals are never merged nor split implicitly.
56
+ * Every interval carries an **object**, given at insertion and handed back by
57
+ every query and traversal. It defaults to `nil`, may be anything, and may be
58
+ shared by several intervals. See [Objects](#objects).
55
59
 
56
60
  ## Operations
57
61
 
@@ -60,13 +64,14 @@ intervals handed to the constructor:
60
64
 
61
65
  | operation | description | complexity |
62
66
  | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------- |
63
- | `DisjointIntervalTree.new(intervals = nil)` | Build an empty tree, or one filled with the given `[a, b]` pairs. | `O(m.log m)` |
64
- | `insert(a, b)` | Add `[a..b[`, raising `OverlapError` if it intersects an already stored interval. | `O(log n)` |
65
- | `insert?(a, b)` | Same as `insert`, but returns `false` instead of raising when it would overlap. | `O(log n)` |
67
+ | `DisjointIntervalTree.new(intervals = nil)` | Build an empty tree, or one filled with the given `[a, b]` or `[a, b, obj]` entries. | `O(m.log m)` |
68
+ | `insert(a, b, obj = nil)` | Add `[a..b[` with its object, raising `OverlapError` if it intersects an already stored interval. | `O(log n)` |
69
+ | `insert?(a, b, obj = nil)` | Same as `insert`, but returns `false` instead of raising when it would overlap. | `O(log n)` |
66
70
  | `intersect(a, b, &blk)` | Yield every stored interval intersecting `[a..b[` in increasing order, or return them as an array when given no block. | `O(k + log n)` |
67
71
  | `intersect?(a, b)` | Whether at least one stored interval intersects `[a..b[`. | `O(log n)` |
68
72
  | `remove(a, b, &blk)` | Remove every stored interval intersecting `[a..b[` - whole, never split - and return how many went. | `O(k.log n)` |
69
- | `at(x)` | The stored interval containing the point `x`, or `nil`. | `O(log n)` |
73
+ | `at(x)` | The stored interval containing the point `x` as `[a, b, obj]`, or `nil`. | `O(log n)` |
74
+ | `[](x)` | The object of the interval containing the point `x`, or `nil`. | `O(log n)` |
70
75
  | `cover?(x)` | Whether the point `x` falls inside a stored interval. | `O(log n)` |
71
76
  | `hull` | The smallest interval enclosing every stored one, read straight off the root augment. | `O(1)` |
72
77
  | `size`, `length` | How many intervals are stored. | `O(1)` |
@@ -74,35 +79,36 @@ intervals handed to the constructor:
74
79
  | `height` | Height of the underlying AVL tree, for tests and diagnostics. | `O(1)` |
75
80
  | `coverage` | Total length covered, that is the sum of the lengths of the stored intervals. | `O(n)` |
76
81
  | `each(&blk)` | Yield every stored interval in increasing order, or return an `Enumerator` when given no block. | `O(n)` |
77
- | `to_a`, `entries` | Every stored interval as an array of `[a, b]` pairs, in increasing order. | `O(n)` |
82
+ | `to_a`, `entries` | Every stored interval as an array of `[a, b, obj]` triples, in increasing order. | `O(n)` |
78
83
  | `map`, `select`, ... | Anything `Enumerable` provides, built on `each`. | `O(n)` |
79
84
  | `clear` | Drop every interval, leaving the tree usable. | `O(n)` |
80
- | `dup` | A deep copy, sharing no node with the original. | `O(n.log n)` |
81
- | `==` | Whether two trees hold exactly the same intervals. | `O(n)` |
85
+ | `dup` | A copy sharing no node with the original, but sharing its objects. | `O(n.log n)` |
86
+ | `==` | Whether two trees hold the same intervals, with objects comparing equal. | `O(n)` |
82
87
  | `check!` | Re-derive every structural invariant, raising `CorruptedError` if one is broken. | `O(n)` |
83
88
  | `inspect`, `to_s` | A short `#<DisjointIntervalTree size=... height=...>` summary. | `O(1)` |
84
89
 
85
90
  ## Ruby API
86
91
 
87
92
  ```ruby
88
- tree = DisjointIntervalTree.new # empty
89
- tree = DisjointIntervalTree.new([[0, 10], [20, 30]]) # pre-filled
93
+ tree = DisjointIntervalTree.new # empty
94
+ tree = DisjointIntervalTree.new([[0, 10], [20, 30, :obj]]) # pre-filled
90
95
 
91
96
  # --- the three core operations -------------------------------------------
92
97
 
93
- tree.insert(a, b) # -> self, raises OverlapError if it overlaps
94
- tree.insert?(a, b) # -> true / false instead of raising
98
+ tree.insert(a, b, obj = nil) # -> self, raises OverlapError if it overlaps
99
+ tree.insert?(a, b, obj = nil) # -> true / false instead of raising
95
100
 
96
- tree.intersect(a, b) { |x, y| ... } # -> self, yields in increasing order
97
- tree.intersect(a, b) # -> [[x, y], ...] when no block given
101
+ tree.intersect(a, b) { |x, y, obj| ... } # -> self, yields increasing order
102
+ tree.intersect(a, b) # -> [[x, y, obj], ...] w/o block
98
103
 
99
- tree.remove(a, b) # -> number of intervals removed
100
- tree.remove(a, b) { |x, y| ... } # ... and yields each removed interval
104
+ tree.remove(a, b) # -> number of intervals removed
105
+ tree.remove(a, b) { |x, y, obj| ... } # ... and yields each removed one
101
106
 
102
107
  # --- queries --------------------------------------------------------------
103
108
 
104
109
  tree.intersect?(a, b) # -> true if any stored interval intersect [a..b[
105
- tree.at(x) # -> [a, b] containing the point x, or nil
110
+ tree.at(x) # -> [a, b, obj] containing the point x, or nil
111
+ tree[x] # -> the object of that interval, or nil
106
112
  tree.cover?(x) # -> true if x is covered by a stored interval
107
113
  tree.hull # -> [a, b] spanning every interval, or nil
108
114
  tree.size # -> number of stored intervals
@@ -111,15 +117,15 @@ tree.height # -> height of the underlying AVL tree
111
117
 
112
118
  # --- iteration (DisjointIntervalTree includes Enumerable) -----------------
113
119
 
114
- tree.each { |a, b| ... }
115
- tree.to_a # -> [[a, b], ...], in increasing order
120
+ tree.each { |a, b, obj| ... }
121
+ tree.to_a # -> [[a, b, obj], ...], in increasing order
116
122
  tree.map { |a, b| b - a }
117
123
  tree.coverage # -> total length covered
118
124
 
119
125
  # --- misc -----------------------------------------------------------------
120
126
 
121
127
  tree.clear # -> self
122
- tree.dup # -> deep copy
128
+ tree.dup # -> copy, sharing the objects
123
129
  tree == other
124
130
  tree.check! # -> self, raises CorruptedError if an invariant
125
131
  # of the underlying tree is broken
@@ -133,6 +139,37 @@ raises `DisjointIntervalTree::Error` rather than corrupting the structure. The
133
139
  block given to `remove` is called after the removal, on a snapshot, so it may
134
140
  modify the tree.
135
141
 
142
+ ## Objects
143
+
144
+ Every interval carries an object. It is given as the third argument of
145
+ `insert`, defaults to `nil`, and comes back - by identity, never copied - from
146
+ every query and traversal:
147
+
148
+ ```ruby
149
+ tree = DisjointIntervalTree.new
150
+ tree.insert(4096, 8192, Allocation.new(:device))
151
+
152
+ tree[6000] # => #<Allocation device> the object at that address
153
+ tree.at(6000) # => [4096, 8192, #<Allocation device>]
154
+ tree.intersect(6000, 12288) { |a, b, alloc| alloc.report(a, b) }
155
+ tree.remove(6000, 12288) { |_a, _b, alloc| alloc.release }
156
+ ```
157
+
158
+ * Any object is accepted, `nil` included, and several intervals may share one.
159
+ * `tree[x]` returns `nil` both when no interval covers `x` and when the
160
+ covering interval holds a `nil` object. Use `at(x)` or `cover?(x)` to tell
161
+ the two apart.
162
+ * The tree holds a **strong reference**: an object stays alive as long as its
163
+ interval is in the tree, and becomes collectable as soon as `remove` or
164
+ `clear` drops it.
165
+ * `dup` is **shallow**: the copy shares the very same objects.
166
+ * A compacting GC may move the objects: the tree updates its references
167
+ accordingly, so identity is preserved across `GC.compact`.
168
+
169
+ In C the object is a plain `void *` that the tree only ever stores and hands
170
+ back - it never reads, copies nor frees it. Pass a callback to `dit_remove()`
171
+ to reclaim objects as their intervals go away.
172
+
136
173
  ## C API
137
174
 
138
175
  The Ruby extension is a thin binding over a standalone, dependency-free C
@@ -142,21 +179,30 @@ is into a C or C++ project.
142
179
  ```c
143
180
  #include "dit.h"
144
181
 
145
- static int print_cb(dit_value_t a, dit_value_t b, void * user)
182
+ static int print_cb(dit_value_t a, dit_value_t b, dit_object_t obj, void * user)
146
183
  {
147
184
  (void) user;
148
- printf("[%lu..%lu[\n", a, b);
185
+ printf("[%lu..%lu[ -> %s\n", a, b, (const char *) obj);
149
186
  return 0; /* non-zero stops the traversal */
150
187
  }
151
188
 
189
+ static int free_cb(dit_value_t a, dit_value_t b, dit_object_t obj, void * user)
190
+ {
191
+ (void) a; (void) b; (void) user;
192
+ free(obj);
193
+ return 0;
194
+ }
195
+
152
196
  dit_t tree;
153
197
  dit_init(&tree);
154
198
 
155
- if (dit_insert(&tree, 0, 10) != DIT_OK)
199
+ if (dit_insert(&tree, 0, 10, strdup("payload")) != DIT_OK)
156
200
  { /* DIT_EMPTY, DIT_OVERLAP or DIT_NOMEM */ }
157
201
 
158
202
  dit_intersect(&tree, 5, 25, print_cb, NULL);
159
- dit_remove(&tree, 5, 25);
203
+
204
+ /* the callback is optional, and lets the caller reclaim the objects */
205
+ dit_remove(&tree, 5, 25, free_cb, NULL);
160
206
 
161
207
  dit_destroy(&tree);
162
208
  ```
@@ -191,6 +237,7 @@ Customization points, to define before including `dit.h`:
191
237
  | macro | default | purpose |
192
238
  | --------------- | ---------------------- | ----------------------------------------------- |
193
239
  | `DIT_VALUE_T` | `uint64_t` | interval bound type |
240
+ | `DIT_OBJECT_T` | `void *` | type of the object associated with each interval |
194
241
  | `DIT_ASSERT` | `assert` | internal consistency assertions |
195
242
  | `DIT_MALLOC` | `malloc` / `free` | node allocation |
196
243
  | `DIT_PARANOID` | `0` | run `dit_check()` after every mutation |
@@ -216,6 +263,12 @@ On top of that, the implementation is littered with `DIT_ASSERT`s on the
216
263
  invariants it relies on locally (rotations, augment refresh, deletion cases,
217
264
  insertion contract, ...).
218
265
 
266
+ Objects are opaque, so no invariant can be derived from them and `dit_check()`
267
+ says nothing about them. Both test suites cover that blind spot instead, by
268
+ storing in every interval an object derived from its lower bound and checking
269
+ it on each reported interval - which is what pins down the one place an object
270
+ could be left behind, the deletion of a node with two children.
271
+
219
272
  ## Building and testing
220
273
 
221
274
  Building the extension needs the ruby development headers (`ruby-dev` /
data/Rakefile CHANGED
@@ -20,10 +20,18 @@ NPROC = Etc.respond_to?(:nprocessors) ? Etc.nprocessors : 4
20
20
  VERSION = DisjointIntervalTree::VERSION
21
21
  GEM_FILE = File.expand_path("pkg/#{EXT_NAME}-#{VERSION}.gem", __dir__)
22
22
 
23
+ # `lib/` holds a single .so, but a C extension only ever works with the ruby
24
+ # ABI it was compiled against: switching ruby - system, spack, rbenv, a CI
25
+ # matrix - has to force a rebuild. Loading the wrong one is not a clean
26
+ # failure, it corrupts memory and crashes somewhere else entirely.
27
+ BUILD_ID_PATH = File.join(LIB_DIR, '.build-id')
28
+ BUILD_ID = [RUBY_ENGINE, RbConfig::CONFIG['ruby_version'], RUBY_PLATFORM,
29
+ (ENV['EXTOPTS'] || '')].join(' ')
30
+
23
31
  SOURCES = FileList["#{EXT_DIR}/*.c", "#{EXT_DIR}/*.h", "#{EXT_DIR}/extconf.rb"]
24
32
 
25
33
  CLEAN.include('tmp')
26
- CLOBBER.include(SO_PATH, 'pkg')
34
+ CLOBBER.include(SO_PATH, BUILD_ID_PATH, 'pkg')
27
35
 
28
36
  # Number of distinct RNG seeds the randomized tests are replayed with by
29
37
  # `rake verify`. Both suites take a seed, and those randomized cross-checks
@@ -37,6 +45,37 @@ EXTOPTS = (ENV['EXTOPTS'] || '').split
37
45
  directory BUILD_DIR
38
46
  directory LIB_DIR
39
47
 
48
+ # Building needs the ruby development headers. Without them mkmf fails deep in
49
+ # the build log with a message about the wrong directory, so say it up front.
50
+ task :check_headers do
51
+ header = File.join(RbConfig::CONFIG['rubyhdrdir'].to_s, 'ruby.h')
52
+ next if File.exist?(header)
53
+
54
+ abort <<~MSG
55
+ #{RUBY_BIN} has no development headers (no #{header}).
56
+
57
+ Install them, or build with a ruby that has them:
58
+ debian/ubuntu : sudo apt install ruby-dev
59
+ spack : spack load ruby
60
+ MSG
61
+ end
62
+
63
+ # Drop an extension built by another ruby rather than letting it be loaded
64
+ task check_build_id: [LIB_DIR, :check_headers] do
65
+ next if File.exist?(BUILD_ID_PATH) && File.read(BUILD_ID_PATH) == BUILD_ID
66
+
67
+ if File.exist?(SO_PATH)
68
+ puts "#{SO_NAME} was built by another ruby (#{begin
69
+ File.read(BUILD_ID_PATH)
70
+ rescue StandardError
71
+ 'unknown'
72
+ end}), rebuilding for #{BUILD_ID}"
73
+ end
74
+
75
+ FileUtils.rm_f(SO_PATH)
76
+ File.write(BUILD_ID_PATH, BUILD_ID)
77
+ end
78
+
40
79
  file SO_PATH => SOURCES + [BUILD_DIR, LIB_DIR] do
41
80
  Dir.chdir(BUILD_DIR) do
42
81
  sh(RUBY_BIN, File.join(EXT_DIR, 'extconf.rb'), *EXTOPTS)
@@ -45,16 +84,21 @@ file SO_PATH => SOURCES + [BUILD_DIR, LIB_DIR] do
45
84
  FileUtils.cp(File.join(BUILD_DIR, SO_NAME), SO_PATH, verbose: true)
46
85
  end
47
86
 
87
+ # The checks come first, as prerequisites rather than as prerequisites of
88
+ # SO_PATH itself: a basic rake task timestamps as `Time.now`, so depending on
89
+ # one would make the file task look out of date on every single run
48
90
  desc 'Build the C extension'
49
- task compile: SO_PATH
91
+ task compile: [:check_headers, :check_build_id, SO_PATH]
50
92
 
51
93
  # `clean` alone would not be enough: rake never considers a file task out of
52
94
  # date because of a directory prerequisite, so the stale .so has to go
53
95
  desc 'Rebuild the C extension from scratch'
54
96
  task :recompile do
97
+ Rake::Task[:check_headers].invoke
55
98
  Rake::Task[:clean].invoke
56
99
  FileUtils.rm_f(SO_PATH)
57
100
  [BUILD_DIR, LIB_DIR, SO_PATH].each { |t| Rake::Task[t].reenable }
101
+ Rake::Task[:check_build_id].invoke
58
102
  Rake::Task[SO_PATH].invoke
59
103
  end
60
104
 
@@ -110,8 +154,9 @@ namespace :test do
110
154
  path = Gem.loaded_specs['disjoint_interval_tree'].full_gem_path
111
155
  raise "loaded \#{path}, expected it under #{GEM_DIR}" unless path.start_with?('#{GEM_DIR}')
112
156
 
113
- tree = DisjointIntervalTree.new([[0, 10], [20, 30]])
114
- raise 'unexpected content' unless tree.intersect(5, 25) == [[0, 10], [20, 30]]
157
+ tree = DisjointIntervalTree.new([[0, 10, :first], [20, 30]])
158
+ raise 'unexpected content' unless tree.intersect(5, 25) == [[0, 10, :first], [20, 30, nil]]
159
+ raise 'unexpected object' unless tree[5] == :first
115
160
  raise 'unexpected removal' unless tree.remove(5, 25) == 2
116
161
  tree.check!
117
162
 
@@ -107,7 +107,7 @@ dit_node_refresh_augment(dit_node_t * node)
107
107
  }
108
108
 
109
109
  static inline dit_node_t *
110
- dit_node_new(dit_value_t a, dit_value_t b)
110
+ dit_node_new(dit_value_t a, dit_value_t b, dit_object_t obj)
111
111
  {
112
112
  DIT_ASSERT(a < b);
113
113
 
@@ -117,6 +117,7 @@ dit_node_new(dit_value_t a, dit_value_t b)
117
117
 
118
118
  node->a = a;
119
119
  node->b = b;
120
+ node->obj = obj;
120
121
  node->left = NULL;
121
122
  node->right = NULL;
122
123
  node->augment.hull.a = a;
@@ -304,22 +305,23 @@ dit_insert_from(
304
305
  dit_node_t * node,
305
306
  dit_value_t a,
306
307
  dit_value_t b,
308
+ dit_object_t obj,
307
309
  dit_status_t * status
308
310
  ) {
309
311
  if (node == NULL)
310
312
  {
311
- dit_node_t * created = dit_node_new(a, b);
313
+ dit_node_t * created = dit_node_new(a, b, obj);
312
314
  *status = created ? DIT_OK : DIT_NOMEM;
313
315
  return created;
314
316
  }
315
317
 
316
318
  /* case (1) - [a..b[ entirely before this node */
317
319
  if (b <= node->a)
318
- node->left = dit_insert_from(node->left, a, b, status);
320
+ node->left = dit_insert_from(node->left, a, b, obj, status);
319
321
 
320
322
  /* case (2) - [a..b[ entirely after this node */
321
323
  else if (a >= node->b)
322
- node->right = dit_insert_from(node->right, a, b, status);
324
+ node->right = dit_insert_from(node->right, a, b, obj, status);
323
325
 
324
326
  /* case (3) - contract violation, [a..b[ intersect this node */
325
327
  else
@@ -338,7 +340,7 @@ dit_insert_from(
338
340
  }
339
341
 
340
342
  dit_status_t
341
- dit_insert(dit_t * tree, dit_value_t a, dit_value_t b)
343
+ dit_insert(dit_t * tree, dit_value_t a, dit_value_t b, dit_object_t obj)
342
344
  {
343
345
  DIT_ASSERT(tree);
344
346
  DIT_ASSERT(tree->traversing == 0 && "cannot mutate the tree while traversing it");
@@ -347,7 +349,7 @@ dit_insert(dit_t * tree, dit_value_t a, dit_value_t b)
347
349
  return DIT_EMPTY;
348
350
 
349
351
  dit_status_t status = DIT_OK;
350
- dit_node_t * root = dit_insert_from(tree->root, a, b, &status);
352
+ dit_node_t * root = dit_insert_from(tree->root, a, b, obj, &status);
351
353
 
352
354
  if (status != DIT_OK)
353
355
  return status;
@@ -385,6 +387,37 @@ dit_intersecting_from(dit_node_t * node, dit_value_t a, dit_value_t b)
385
387
  return NULL;
386
388
  }
387
389
 
390
+ /* Same, but always returning the *smallest* such interval. It cannot stop as
391
+ * soon as it finds a match, so it always walks a full root-to-leaf path -
392
+ * still O(log n), just without the early exit */
393
+ static inline dit_node_t *
394
+ dit_leftmost_intersecting_from(dit_node_t * node, dit_value_t a, dit_value_t b)
395
+ {
396
+ dit_node_t * leftmost = NULL;
397
+
398
+ /* leftmost node ending after `a`. Intervals being disjoint and ordered,
399
+ * those intersecting [a..b[ form a contiguous run, so that node is the
400
+ * first of the run - when it starts before `b` */
401
+ while (node)
402
+ {
403
+ if (node->b > a)
404
+ {
405
+ leftmost = node;
406
+ node = node->left;
407
+ }
408
+ else
409
+ node = node->right;
410
+ }
411
+
412
+ if (leftmost && leftmost->a < b)
413
+ {
414
+ DIT_ASSERT(DIT_INTERSECTS(a, b, leftmost->a, leftmost->b));
415
+ return leftmost;
416
+ }
417
+
418
+ return NULL;
419
+ }
420
+
388
421
  const dit_node_t *
389
422
  dit_intersecting(const dit_t * tree, dit_value_t a, dit_value_t b)
390
423
  {
@@ -440,7 +473,7 @@ dit_intersect_from(
440
473
 
441
474
  if (DIT_INTERSECTS(a, b, node->a, node->b))
442
475
  {
443
- if ((r = cb(node->a, node->b, user)) != 0)
476
+ if ((r = cb(node->a, node->b, node->obj, user)) != 0)
444
477
  return r;
445
478
  }
446
479
 
@@ -476,7 +509,7 @@ dit_each_from(dit_node_t * node, dit_cb_t cb, void * user)
476
509
  if ((r = dit_each_from(node->left, cb, user)) != 0)
477
510
  return r;
478
511
 
479
- if ((r = cb(node->a, node->b, user)) != 0)
512
+ if ((r = cb(node->a, node->b, node->obj, user)) != 0)
480
513
  return r;
481
514
 
482
515
  return dit_each_from(node->right, cb, user);
@@ -545,15 +578,20 @@ dit_remove_from(dit_node_t * node, dit_value_t key)
545
578
  }
546
579
 
547
580
  /* two children: replace the interval with its in-order successor's,
548
- * then remove that successor from the right subtree */
581
+ * then remove that successor from the right subtree.
582
+ *
583
+ * The object travels with the interval it belongs to: forgetting it
584
+ * here would silently hand the successor's interval the object of the
585
+ * node being deleted */
549
586
  dit_node_t * successor;
550
587
  node->right = dit_detach_min(node->right, &successor);
551
588
 
552
589
  DIT_ASSERT(successor && successor->left == NULL);
553
590
  DIT_ASSERT(node->a < successor->a);
554
591
 
555
- node->a = successor->a;
556
- node->b = successor->b;
592
+ node->a = successor->a;
593
+ node->b = successor->b;
594
+ node->obj = successor->obj;
557
595
 
558
596
  DIT_FREE(successor);
559
597
  }
@@ -563,7 +601,7 @@ dit_remove_from(dit_node_t * node, dit_value_t key)
563
601
  }
564
602
 
565
603
  size_t
566
- dit_remove(dit_t * tree, dit_value_t a, dit_value_t b)
604
+ dit_remove(dit_t * tree, dit_value_t a, dit_value_t b, dit_cb_t cb, void * user)
567
605
  {
568
606
  DIT_ASSERT(tree);
569
607
  DIT_ASSERT(tree->traversing == 0 && "cannot mutate the tree while traversing it");
@@ -573,13 +611,23 @@ dit_remove(dit_t * tree, dit_value_t a, dit_value_t b)
573
611
 
574
612
  size_t n = 0;
575
613
 
576
- /* each iteration is a O(log n) descent plus a O(log n) deletion */
614
+ /* each iteration is a O(log n) descent plus a O(log n) deletion. Removing
615
+ * the smallest match every time reports them in increasing order */
577
616
  for (;;)
578
617
  {
579
- dit_node_t * node = dit_intersecting_from(tree->root, a, b);
618
+ dit_node_t * node = dit_leftmost_intersecting_from(tree->root, a, b);
580
619
  if (node == NULL)
581
620
  break ;
582
621
 
622
+ if (cb)
623
+ {
624
+ /* the callback may read the tree, but not mutate it: it would
625
+ * free the very node about to be deleted */
626
+ tree->traversing += 1;
627
+ cb(node->a, node->b, node->obj, user);
628
+ tree->traversing -= 1;
629
+ }
630
+
583
631
  tree->root = dit_remove_from(tree->root, node->a);
584
632
 
585
633
  DIT_ASSERT(tree->n > 0);
@@ -821,6 +869,7 @@ dit_dump_dot_from(const dit_node_t * node, FILE * f)
821
869
 
822
870
  fprintf(f, " N%p[shape=record, label=\"{[%" DIT_VALUE_FMT "..%" DIT_VALUE_FMT "[",
823
871
  (const void *) node, node->a, node->b);
872
+ fprintf(f, "|obj %p", (const void *) (uintptr_t) node->obj);
824
873
  fprintf(f, "|hull [%" DIT_VALUE_FMT "..%" DIT_VALUE_FMT "[",
825
874
  node->augment.hull.a, node->augment.hull.b);
826
875
  fprintf(f, "|h=%d, n=%u}\"] ;\n", (int) node->augment.height, node->augment.size);
@@ -34,6 +34,12 @@
34
34
  ** insertion descent: `DIT_OVERLAP` is returned and the tree is left
35
35
  ** untouched.
36
36
  **
37
+ ** Objects
38
+ ** Every interval carries an opaque object, given at insertion and handed
39
+ ** back by every query and traversal. The tree never owns it: it only
40
+ ** stores the value, and never reads, copies nor releases whatever it
41
+ ** points to. See `dit_remove()` to reclaim objects as intervals go away.
42
+ **
37
43
  ** Complexities, with `n` intervals stored and `k` intervals reported
38
44
  ** dit_insert O(log n)
39
45
  ** dit_intersect O(k + log n)
@@ -65,6 +71,15 @@ extern "C" {
65
71
 
66
72
  typedef DIT_VALUE_T dit_value_t;
67
73
 
74
+ /* Type of the object associated with each interval. It is opaque to the tree,
75
+ * which only ever stores and hands it back */
76
+ # ifndef DIT_OBJECT_T
77
+ # define DIT_OBJECT_T void *
78
+ # define DIT_OBJECT_NULL ((dit_object_t) NULL)
79
+ # endif /* DIT_OBJECT_T */
80
+
81
+ typedef DIT_OBJECT_T dit_object_t;
82
+
68
83
  /* Internal consistency assertions. Those only ever fire on a dit bug, never on
69
84
  * a caller mistake - caller mistakes are reported through `dit_status_t`.
70
85
  * Define `DIT_ASSERT` to override, or `NDEBUG` to compile them out */
@@ -139,6 +154,10 @@ typedef struct dit_node_s
139
154
  /* the interval [a..b[ represented by this node, with a < b */
140
155
  dit_value_t a, b;
141
156
 
157
+ /* the object associated with that interval, as given to `dit_insert()`.
158
+ * Opaque to the tree, which never dereferences nor releases it */
159
+ dit_object_t obj;
160
+
142
161
  /* children - `child[DIT_LEFT]` holds intervals entirely before `a`,
143
162
  * `child[DIT_RIGHT]` holds intervals entirely after `b` */
144
163
  union {
@@ -166,15 +185,18 @@ typedef struct dit_s
166
185
  } dit_t;
167
186
 
168
187
  /* Interval callback.
169
- * `[a..b[` is the stored interval, `user` the opaque pointer given to the
170
- * traversal. Return 0 to keep going, non-zero to stop the traversal early -
171
- * that value is then returned by the traversal routine */
172
- typedef int (*dit_cb_t)(dit_value_t a, dit_value_t b, void * user);
188
+ * `[a..b[` is the stored interval and `obj` its associated object, while
189
+ * `user` is the opaque pointer given to the traversal. Return 0 to keep going,
190
+ * non-zero to stop the traversal early - that value is then returned by the
191
+ * traversal routine */
192
+ typedef int (*dit_cb_t)(dit_value_t a, dit_value_t b, dit_object_t obj, void * user);
173
193
 
174
194
  /* Initialize an empty tree. `dit_t` may also be zero-initialized */
175
195
  void dit_init(dit_t * tree);
176
196
 
177
- /* Free every node. The tree is left initialized and empty */
197
+ /* Free every node. The tree is left initialized and empty.
198
+ * Objects are *not* released: walk the tree with `dit_each()` first if they
199
+ * need to be reclaimed */
178
200
  void dit_clear(dit_t * tree);
179
201
 
180
202
  /* Alias of `dit_clear()`, for symmetry with `dit_init()` */
@@ -194,11 +216,11 @@ int dit_height(const dit_t * tree);
194
216
  * untouched when the tree is empty. O(1) */
195
217
  int dit_hull(const dit_t * tree, dit_value_t * a, dit_value_t * b);
196
218
 
197
- /* Insert `[a..b[`.
219
+ /* Insert `[a..b[`, associated with the object `obj`.
198
220
  * Returns DIT_OK, DIT_EMPTY if `a >= b`, DIT_OVERLAP if `[a..b[` intersects an
199
221
  * already inserted interval, DIT_NOMEM on allocation failure. The tree is left
200
222
  * unchanged unless DIT_OK is returned */
201
- dit_status_t dit_insert(dit_t * tree, dit_value_t a, dit_value_t b);
223
+ dit_status_t dit_insert(dit_t * tree, dit_value_t a, dit_value_t b, dit_object_t obj);
202
224
 
203
225
  /* Return the stored interval intersecting `[a..b[`, or NULL if there is none.
204
226
  * If several intervals intersect `[a..b[`, which one is returned is
@@ -223,8 +245,13 @@ int dit_each(dit_t * tree, dit_cb_t cb, void * user);
223
245
 
224
246
  /* Remove every stored interval intersecting `[a..b[`. Returns how many
225
247
  * intervals were removed. Removed intervals are removed as a whole: an
226
- * interval merely overlapping `[a..b[` is *not* split */
227
- size_t dit_remove(dit_t * tree, dit_value_t a, dit_value_t b);
248
+ * interval merely overlapping `[a..b[` is *not* split.
249
+ *
250
+ * `cb`, when not NULL, is invoked on each interval just before its node is
251
+ * freed, so that its object can be reclaimed. It is called in increasing
252
+ * order, must not mutate the tree, and - unlike a traversal callback - its
253
+ * return value is ignored: a removal cannot be interrupted halfway */
254
+ size_t dit_remove(dit_t * tree, dit_value_t a, dit_value_t b, dit_cb_t cb, void * user);
228
255
 
229
256
  /* Verify every structural invariant of the tree.
230
257
  * Returns 0 if the tree is coherent. Otherwise returns a non-zero value and,