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 +4 -4
- data/README.md +82 -29
- data/Rakefile +49 -4
- data/ext/disjoint_interval_tree/dit.c +63 -14
- data/ext/disjoint_interval_tree/dit.h +36 -9
- data/ext/disjoint_interval_tree/dit_ruby.c +157 -40
- data/lib/disjoint_interval_tree/version.rb +1 -1
- data/lib/disjoint_interval_tree.rb +11 -6
- data/test/c/test_dit.c +335 -65
- data/test/test_disjoint_interval_tree.rb +225 -36
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b89b66e1ed427b22939713e6e3d8cdcaf25b3ea9d5ce352a83fc785a17abde66
|
|
4
|
+
data.tar.gz: 4885d5e8432c76ce4b8f37c78efdc9981f4815fa9baf60f791e058480aa8ae17
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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]`
|
|
64
|
-
| `insert(a, b)`
|
|
65
|
-
| `insert?(a, b)`
|
|
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`.
|
|
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]`
|
|
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
|
|
81
|
-
| `==` | Whether two trees hold
|
|
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
|
|
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)
|
|
94
|
-
tree.insert?(a, b)
|
|
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| ... }
|
|
97
|
-
tree.intersect(a, b)
|
|
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)
|
|
100
|
-
tree.remove(a, b) { |x, y| ... }
|
|
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 # ->
|
|
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
|
-
|
|
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
|
|
556
|
-
node->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 =
|
|
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
|
|
170
|
-
* traversal. Return 0 to keep going,
|
|
171
|
-
* that value is then returned by the
|
|
172
|
-
|
|
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
|
-
|
|
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,
|