interval_set 0.1.0.pre.RC1
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/.gitignore +10 -0
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +39 -0
- data/LICENSE.txt +21 -0
- data/README.md +271 -0
- data/Rakefile +9 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/interval_set.gemspec +28 -0
- data/lib/interval_set/version.rb +3 -0
- data/lib/interval_set.rb +995 -0
- metadata +120 -0
data/lib/interval_set.rb
ADDED
@@ -0,0 +1,995 @@
|
|
1
|
+
require_relative 'interval_set/version'
|
2
|
+
require 'treemap-fork'
|
3
|
+
|
4
|
+
# IntervalSet implements a set of sorted non-overlapping ranges.
|
5
|
+
# A range's start is always interpreted as inclusive while the end is exclusive
|
6
|
+
class IntervalSet
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
# Builds a new IntervalSet from the supplied ranges. Overlapping ranges will be merged.
|
10
|
+
# IntervalSet[] # -> []
|
11
|
+
# IntervalSet[0...1] # -> [0...1]
|
12
|
+
# IntervalSet[0...1, 2...3] # -> [0...1, 2...3]
|
13
|
+
# IntervalSet[0...1, 1...2] # -> [0...2]
|
14
|
+
#
|
15
|
+
# array = [0...1, 2...3]
|
16
|
+
# IntervalSet[*array] # -> [0...1, 2...3]
|
17
|
+
#
|
18
|
+
# @param ranges [Range[]] a list of ranges to be added to the new IntervalSet
|
19
|
+
# @return [IntervalSet] a new IntervalSet containing the supplied ranges.
|
20
|
+
def self.[](*ranges)
|
21
|
+
IntervalSet.new.tap do |interval_set|
|
22
|
+
ranges.each {|range| interval_set << range}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns an empty instance of IntervalSet.
|
27
|
+
# @param range_map [TreeMap] a TreeMap of ranges. For internal use only.
|
28
|
+
def initialize(range_map = TreeMap.new)
|
29
|
+
unless range_map.instance_of?(TreeMap) || range_map.instance_of?(TreeMap::BoundedMap)
|
30
|
+
raise ArgumentError.new("invalid range_map #{range_map}")
|
31
|
+
end
|
32
|
+
|
33
|
+
@range_map = range_map
|
34
|
+
|
35
|
+
update_bounds
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns +true+ if this IntervalSet contains no ranges.
|
39
|
+
def empty?
|
40
|
+
@range_map.empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the lower bound of this IntervalSet.
|
44
|
+
#
|
45
|
+
# IntervalSet[0...1, 2...3].min # -> 0
|
46
|
+
# IntervalSet[].min # -> nil
|
47
|
+
#
|
48
|
+
# @return the lower bound or +nil+ if empty.
|
49
|
+
def min
|
50
|
+
@min
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the upper bound of this IntervalSet.
|
54
|
+
#
|
55
|
+
# IntervalSet[0...1, 2...3].max # -> 3
|
56
|
+
# IntervalSet[].max # -> nil
|
57
|
+
#
|
58
|
+
# @return the upper bound or +nil+ if empty.
|
59
|
+
def max
|
60
|
+
@max
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the bounds of this IntervalSet.
|
64
|
+
#
|
65
|
+
# IntervalSet[0...1, 2...3].bounds # -> 0...3
|
66
|
+
# IntervalSet[].bounds # -> nil
|
67
|
+
#
|
68
|
+
# @return [Range] a range from lower to upper bound or +nil+ if empty.
|
69
|
+
def bounds
|
70
|
+
empty? ? nil : min...max
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns +true+ if two IntervalSets are equal.
|
74
|
+
#
|
75
|
+
# IntervalSet[0...1] == IntervalSet[0...1] # -> true
|
76
|
+
# IntervalSet[0...1] == IntervalSet[1...2] # -> false
|
77
|
+
#
|
78
|
+
# @param other [Object] the other object.
|
79
|
+
def eql?(other)
|
80
|
+
return false if other.nil? || !other.is_a?(IntervalSet) || count != other.count || bounds != other.bounds
|
81
|
+
|
82
|
+
lhs_iter = enum_for
|
83
|
+
rhs_iter = other.enum_for
|
84
|
+
|
85
|
+
count.times.all? {lhs_iter.next == rhs_iter.next}
|
86
|
+
end
|
87
|
+
|
88
|
+
alias_method :==, :eql?
|
89
|
+
|
90
|
+
# Returns +true+ if the other object represents a equal
|
91
|
+
# set of ranges as this IntervalSet.
|
92
|
+
#
|
93
|
+
# IntervalSet[1...2].eql_set?(1...2) # -> true
|
94
|
+
# IntervalSet[1...2].eql_set?(IntervalSet[1...2]) # -> true
|
95
|
+
#
|
96
|
+
# @param other [Range | IntervalSet] the other object.
|
97
|
+
def eql_set?(other)
|
98
|
+
case other
|
99
|
+
when Range
|
100
|
+
eql_range?(other)
|
101
|
+
when IntervalSet
|
102
|
+
eql?(other)
|
103
|
+
else
|
104
|
+
IntervalSet.unexpected_object(other)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns +true+ if this IntervalSet contains the given element.
|
109
|
+
#
|
110
|
+
# i = IntervalSet[0...1] # -> [0...1]
|
111
|
+
#
|
112
|
+
# i.include?(0) # -> true
|
113
|
+
# i.include?(0.5) # -> true
|
114
|
+
# i.include?(1) # -> false ; a range's end is exclusive
|
115
|
+
#
|
116
|
+
# Note that the given element must be comparable to elements already in this
|
117
|
+
# set. Otherwise, the behavior is undefined.
|
118
|
+
#
|
119
|
+
# @param element [Object]
|
120
|
+
def include?(element)
|
121
|
+
return false if element.nil?
|
122
|
+
|
123
|
+
floor_entry = @range_map.floor_entry(element)
|
124
|
+
|
125
|
+
!floor_entry.nil? && floor_entry.value.last > element
|
126
|
+
end
|
127
|
+
|
128
|
+
alias_method :===, :include?
|
129
|
+
|
130
|
+
# Returns +true+ if this IntervalSet includes all elements
|
131
|
+
# of the other object.
|
132
|
+
#
|
133
|
+
# IntervalSet[0...1] >= IntervalSet[0...1] # -> true
|
134
|
+
# IntervalSet[0...2] >= IntervalSet[0...1] # -> true
|
135
|
+
# IntervalSet[0...1] >= IntervalSet[0...1, 2...3] # -> false
|
136
|
+
# IntervalSet[0...3] >= IntervalSet[0...1, 2...3] # -> true
|
137
|
+
#
|
138
|
+
# # You can also supply ranges
|
139
|
+
# IntervalSet[0...2].superset?(0...1) # -> true
|
140
|
+
#
|
141
|
+
# @param other [Range | IntervalSet] the other object.
|
142
|
+
def superset?(other)
|
143
|
+
case other
|
144
|
+
when Range
|
145
|
+
superset_range?(other)
|
146
|
+
when IntervalSet
|
147
|
+
superset_interval_set?(other)
|
148
|
+
else
|
149
|
+
IntervalSet.unexpected_object(other)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
alias_method :>=, :superset?
|
154
|
+
|
155
|
+
# Returns +true+ if all elements of this IntervalSet are
|
156
|
+
# included by the other object.
|
157
|
+
#
|
158
|
+
# IntervalSet[0...1] <= IntervalSet[0...1] # -> true
|
159
|
+
# IntervalSet[0...1] <= IntervalSet[0...1, 2...3] # -> true
|
160
|
+
# IntervalSet[0...1, 2...3] <= IntervalSet[0...1] # -> false
|
161
|
+
# IntervalSet[0...1, 2...3] <= IntervalSet[0...3] # -> true
|
162
|
+
#
|
163
|
+
# # You can also supply ranges
|
164
|
+
# IntervalSet[0...1, 2...3].subset?(0...3) # -> true
|
165
|
+
#
|
166
|
+
# @param other [Range | IntervalSet] the other object.
|
167
|
+
def subset?(other)
|
168
|
+
case other
|
169
|
+
when Range
|
170
|
+
subset_range?(other)
|
171
|
+
when IntervalSet
|
172
|
+
other.superset_interval_set?(self)
|
173
|
+
else
|
174
|
+
IntervalSet.unexpected_object(other)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
alias_method :<=, :subset?
|
179
|
+
|
180
|
+
# Returns +true+ if this IntervalSet is a proper superset of the other.
|
181
|
+
#
|
182
|
+
# IntervalSet[0...2] > IntervalSet[0...1] # -> true
|
183
|
+
# IntervalSet[0...2] > IntervalSet[0...2] # -> false
|
184
|
+
# IntervalSet[0...2] > IntervalSet[1...3] # -> false
|
185
|
+
#
|
186
|
+
# # Compare to ranges
|
187
|
+
# IntervalSet[0...3].superset?(1...2) # -> true
|
188
|
+
#
|
189
|
+
# @param other [Range | IntervalSet] the other object.
|
190
|
+
def proper_superset?(other)
|
191
|
+
!eql_set?(other) && superset?(other)
|
192
|
+
end
|
193
|
+
|
194
|
+
alias_method :>, :proper_superset?
|
195
|
+
|
196
|
+
# Return +true+ if this IntervalSet is a proper subset of the other.
|
197
|
+
#
|
198
|
+
# IntervalSet[0...1] < IntervalSet[0...2] # -> true
|
199
|
+
# IntervalSet[1...3] < IntervalSet[0...2] # -> false
|
200
|
+
# IntervalSet[1...3] < IntervalSet[0...2] # -> false
|
201
|
+
#
|
202
|
+
# # Compare to ranges
|
203
|
+
# IntervalSet[1...2].subset?(0...3) # -> false
|
204
|
+
#
|
205
|
+
# @param other [Range | IntervalSet] the other object.
|
206
|
+
def proper_subset?(other)
|
207
|
+
!eql_set?(other) && subset?(other)
|
208
|
+
end
|
209
|
+
|
210
|
+
alias_method :<, :proper_subset?
|
211
|
+
|
212
|
+
# Returns +true+ if the given range has common elements with the
|
213
|
+
# bounding range of this IntervalSet.
|
214
|
+
#
|
215
|
+
# IntervalSet[1...2].bounds_intersected_by?(2...3) # -> false
|
216
|
+
# IntervalSet[1...2, 5...6].bounds_intersected_by?(3...4) # -> true
|
217
|
+
#
|
218
|
+
# @param range [Range]
|
219
|
+
def bounds_intersected_by?(range)
|
220
|
+
return false if IntervalSet.range_empty?(range)
|
221
|
+
|
222
|
+
!empty? && range.first < max && range.last > min
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns +true+ if the given range has common elements with the
|
226
|
+
# bounding range or the bounds of this IntervalSet.
|
227
|
+
#
|
228
|
+
# IntervalSet[1...2].bounds_intersected_or_touched_by?(2...3) # -> true
|
229
|
+
# IntervalSet[1...2].bounds_intersected_or_touched_by?(3...4) # -> false
|
230
|
+
# IntervalSet[1...2, 5...6].bounds_intersected_or_touched_by?(3...4) # -> true
|
231
|
+
#
|
232
|
+
# @param range [Range]
|
233
|
+
def bounds_intersected_or_touched_by?(range)
|
234
|
+
return false if IntervalSet.range_empty?(range)
|
235
|
+
|
236
|
+
!empty? && range.first <= max && range.last >= min
|
237
|
+
end
|
238
|
+
|
239
|
+
# Returns +true+ if the given object has any common elements with
|
240
|
+
# this IntervalSet.
|
241
|
+
#
|
242
|
+
# i = IntervalSet[0...1] # -> [0...1]
|
243
|
+
#
|
244
|
+
# # Ranges only need a single common element with the interval set
|
245
|
+
# i.intersect?(0...1) # -> true
|
246
|
+
# i.intersect?(0...2) # -> true
|
247
|
+
# i.intersect?(1...2) # -> false ; the start of a range is inclusive but the end exclusive
|
248
|
+
#
|
249
|
+
# # The same applies for interval sets
|
250
|
+
# i.intersect?(IntervalSet[0...1]) # -> true
|
251
|
+
# i.intersect?(IntervalSet[0...1, 2...3]) # -> true
|
252
|
+
# i.intersect?(IntervalSet[2...3]) # -> false
|
253
|
+
#
|
254
|
+
# @param other [Range | IntervalSet] the other object.
|
255
|
+
def intersect?(other)
|
256
|
+
case other
|
257
|
+
when Range
|
258
|
+
intersect_range?(other)
|
259
|
+
when IntervalSet
|
260
|
+
intersect_interval_set?(other)
|
261
|
+
else
|
262
|
+
IntervalSet.unexpected_object(other)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Counts the number of ranges contained by this IntervalSet.
|
267
|
+
#
|
268
|
+
# i = IntervalSet[] # -> []
|
269
|
+
# i.count # -> 0
|
270
|
+
# i << (0...1) # -> [0...1]
|
271
|
+
# i.count # -> 1
|
272
|
+
# i << (2...3) # -> [0...1, 2...3]
|
273
|
+
# i.count # -> 2
|
274
|
+
# i << (1...2) # -> [0...3]
|
275
|
+
# i.count # -> 1
|
276
|
+
#
|
277
|
+
# @return [Fixnum] the number of ranges.
|
278
|
+
def count
|
279
|
+
@range_map.count
|
280
|
+
end
|
281
|
+
|
282
|
+
# Adds the other object's elements to this IntervalSet.
|
283
|
+
# The result is stored in this IntervalSet.
|
284
|
+
#
|
285
|
+
# IntervalSet.new.add(0...1) # -> [0...1]
|
286
|
+
# IntervalSet.new << (0...1) # -> [0...1]
|
287
|
+
#
|
288
|
+
# i = IntervalSet.new # -> []
|
289
|
+
# i << (0...1) # -> [0...1]
|
290
|
+
# i << (2...3) # -> [0...1, 2...3]
|
291
|
+
# i << (1...2) # -> [0...3]
|
292
|
+
# i << (-1...4) # -> [-1...4]
|
293
|
+
#
|
294
|
+
# @param other [Range, IntervalSet] the other object.
|
295
|
+
# @return [IntervalSet] self.
|
296
|
+
def add(other)
|
297
|
+
case other
|
298
|
+
when Range
|
299
|
+
add_range(other)
|
300
|
+
when IntervalSet
|
301
|
+
add_interval_set(other)
|
302
|
+
else
|
303
|
+
IntervalSet.unexpected_object(other)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
alias_method :<<, :add
|
308
|
+
alias_method :union!, :add
|
309
|
+
|
310
|
+
# Removes the other object's elements from this IntervalSet.
|
311
|
+
# The result is stored in this IntervalSet.
|
312
|
+
#
|
313
|
+
# i = IntervalSet[0...10] # -> [0...10]
|
314
|
+
# i.remove(0...2) # -> [8...10]
|
315
|
+
# i >> (2...8) # -> [0...2, 8...10]
|
316
|
+
#
|
317
|
+
# @param other [Range, IntervalSet] the other object.
|
318
|
+
# @return [IntervalSet] self.
|
319
|
+
def remove(other)
|
320
|
+
case other
|
321
|
+
when Range
|
322
|
+
remove_range(other)
|
323
|
+
when IntervalSet
|
324
|
+
remove_interval_set(other)
|
325
|
+
else
|
326
|
+
IntervalSet.unexpected_object(other)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
alias_method :>>, :remove
|
331
|
+
alias_method :difference!, :remove
|
332
|
+
|
333
|
+
# Intersects the other object's elements with this IntervalSet.
|
334
|
+
# The result is stored in this IntervalSet.
|
335
|
+
#
|
336
|
+
# i = IntervalSet[0...2, 3...5].intersect(1...5) # -> [1...2, 3...5]
|
337
|
+
# i # -> [1...2, 3...5]
|
338
|
+
#
|
339
|
+
# @param other [Range, IntervalSet] the other object.
|
340
|
+
# @return [IntervalSet] self.
|
341
|
+
def intersect(other)
|
342
|
+
case other
|
343
|
+
when Range
|
344
|
+
intersect_range(other)
|
345
|
+
when IntervalSet
|
346
|
+
intersect_interval_set(other)
|
347
|
+
else
|
348
|
+
IntervalSet.unexpected_object(other)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
alias_method :intersection!, :intersect
|
353
|
+
|
354
|
+
# Intersects the other object's elements with this IntervalSet.
|
355
|
+
# The result is stored in a new IntervalSet.
|
356
|
+
#
|
357
|
+
# IntervalSet[0...2, 3...5] & IntervalSet[1...4, 5...6] # -> [1...2, 3...4]
|
358
|
+
#
|
359
|
+
# @param other [Range, IntervalSet] the other object.
|
360
|
+
# @return [IntervalSet] a new IntervalSet containing the intersection.
|
361
|
+
def intersection(other)
|
362
|
+
case other
|
363
|
+
when Range
|
364
|
+
intersection_range(other)
|
365
|
+
when IntervalSet
|
366
|
+
intersection_interval_set(other)
|
367
|
+
else
|
368
|
+
IntervalSet.unexpected_object(other)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
alias_method :&, :intersection
|
373
|
+
|
374
|
+
# Joins the other object's elements with this IntervalSet.
|
375
|
+
# The result is stored in a new IntervalSet.
|
376
|
+
#
|
377
|
+
# IntervalSet[0...1, 2...3] | IntervalSet[1...2, 4...5] # -> [0...3, 4...5]
|
378
|
+
#
|
379
|
+
# Note that using +add+ or +union!+ is more efficient than
|
380
|
+
# <code>+=</code> or <code>|=</code>.
|
381
|
+
#
|
382
|
+
# @param other [Range, IntervalSet] the other object.
|
383
|
+
# @return [IntervalSet] a new IntervalSet containing the union.
|
384
|
+
def union(other)
|
385
|
+
case other
|
386
|
+
when Range
|
387
|
+
union_range(other)
|
388
|
+
when IntervalSet
|
389
|
+
union_interval_set(other)
|
390
|
+
else
|
391
|
+
IntervalSet.unexpected_object(other)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
alias_method :|, :union
|
396
|
+
alias_method :+, :union
|
397
|
+
|
398
|
+
# Subtracts the other object's elements from this IntervalSet.
|
399
|
+
# The result is stored in a new IntervalSet.
|
400
|
+
#
|
401
|
+
# IntervalSet[0...2, 3...5] - IntervalSet[1...4, 5...6] # -> [0...1, 4...5]
|
402
|
+
#
|
403
|
+
# Note that using +remove+ or +difference!+ is more efficient
|
404
|
+
# than <code>-=</code>.
|
405
|
+
#
|
406
|
+
# @param other [Range, IntervalSet] the other object.
|
407
|
+
# @return [IntervalSet] a new IntervalSet containing the difference.
|
408
|
+
def difference(other)
|
409
|
+
case other
|
410
|
+
when Range
|
411
|
+
difference_range(other)
|
412
|
+
when IntervalSet
|
413
|
+
difference_interval_set(other)
|
414
|
+
else
|
415
|
+
IntervalSet.unexpected_object(other)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
alias_method :-, :difference
|
420
|
+
|
421
|
+
# Calculates a new IntervalSet which only contains elements exclusively from
|
422
|
+
# either this or the given object.
|
423
|
+
#
|
424
|
+
# This operation is equivalent to <code>(self | other) - (self & other)</code>
|
425
|
+
#
|
426
|
+
# IntervalSet[0...1] ^ IntervalSet[1...2] # -> [0...2]
|
427
|
+
# IntervalSet[0...2, 4...6] ^ IntervalSet[1...5, 7...8] # -> [0...1, 2...4, 5...6, 7...8]
|
428
|
+
# IntervalSet[0...1] ^ IntervalSet[0...1] # -> []
|
429
|
+
#
|
430
|
+
# @param other [Range, IntervalSet]
|
431
|
+
# @return [IntervalSet] a new IntervalSet containing the exclusive set.
|
432
|
+
def xor(other)
|
433
|
+
clone.xor!(other)
|
434
|
+
end
|
435
|
+
|
436
|
+
alias_method :^, :xor
|
437
|
+
|
438
|
+
# Calculates the set which contains elements exclusively from
|
439
|
+
# either this or the given object. The result of this operation
|
440
|
+
# is stored in this set.
|
441
|
+
#
|
442
|
+
# The resulting set is equivalent to <code>(self | other) - (self & other)</code>
|
443
|
+
#
|
444
|
+
# IntervalSet[0...1].xor!(IntervalSet[1...2]) # -> [0...2]
|
445
|
+
# IntervalSet[0...2, 4...6].xor!(IntervalSet[1...5, 7...8]) # -> [0...1, 2...4, 5...6, 7...8]
|
446
|
+
# IntervalSet[0...1].xor!(IntervalSet[0...1]) # -> []
|
447
|
+
#
|
448
|
+
# @param other [Range, IntervalSet]
|
449
|
+
# @return [IntervalSet] a new IntervalSet containing the exclusive set.
|
450
|
+
def xor!(other)
|
451
|
+
intersection = self & other
|
452
|
+
|
453
|
+
add(other).remove(intersection)
|
454
|
+
end
|
455
|
+
|
456
|
+
# Convolves the other object's elements with this IntervalSet.
|
457
|
+
# The result is stored in this IntervalSet.
|
458
|
+
#
|
459
|
+
# The result will contain all elements which can be obtained by adding
|
460
|
+
# any pair of elements from both sets. A ∗ B = { a + b | a ∈ A ∧ b ∈ B }
|
461
|
+
#
|
462
|
+
# # Convolve with a range (effectively buffers the set)
|
463
|
+
# IntervalSet[0...4].convolve!(-1...2) # -> [-1...6]
|
464
|
+
#
|
465
|
+
# # Convolving with empty or reversed ranges result in an empty set.
|
466
|
+
# IntervalSet[0...4].convolve!(0...0) # -> []
|
467
|
+
# IntervalSet[0...4].convolve!(1...0) # -> []
|
468
|
+
#
|
469
|
+
# # Convolve with a interval set
|
470
|
+
# IntervalSet[0...1, 10...12].convolve!(IntervalSet[-2...1, 1...2]) # -> [-2...3, 8...14]
|
471
|
+
#
|
472
|
+
# @param other [Range | IntervalSet] the other object.
|
473
|
+
# @return [IntervalSet] self
|
474
|
+
def convolve!(other)
|
475
|
+
case other
|
476
|
+
when Range
|
477
|
+
convolve_range!(other)
|
478
|
+
when IntervalSet
|
479
|
+
convolve_interval_set!(other)
|
480
|
+
else
|
481
|
+
IntervalSet.unexpected_object(other)
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
# Convolves the other object's elements with this IntervalSet.
|
486
|
+
# The result is stored in a new IntervalSet.
|
487
|
+
#
|
488
|
+
# The result will contain all elements which can be obtained by adding
|
489
|
+
# any pair of elements from both sets. A ∗ B = { a + b | a ∈ A ∧ b ∈ B }
|
490
|
+
#
|
491
|
+
# # Convolve with a range (effectively buffers the set)
|
492
|
+
# IntervalSet[0...4] * (-1...2) # -> [-1...6]
|
493
|
+
#
|
494
|
+
# # Convolving with empty or reversed ranges result in an empty set.
|
495
|
+
# IntervalSet[0...4] * (0...0) # -> []
|
496
|
+
# IntervalSet[0...4] * (1...0) # -> []
|
497
|
+
#
|
498
|
+
# # Convolve with a interval set
|
499
|
+
# IntervalSet[0...1, 10...12] * IntervalSet[-2...1, 1...2] # -> [-2...3, 8...14]
|
500
|
+
#
|
501
|
+
# @param other [Range | IntervalSet] the other object.
|
502
|
+
# @return [IntervalSet] a new IntervalSet containing the convolution.
|
503
|
+
def convolve(other)
|
504
|
+
clone.convolve!(other)
|
505
|
+
end
|
506
|
+
|
507
|
+
alias_method :*, :convolve
|
508
|
+
|
509
|
+
# Shifts this IntervalSet by the given amount.
|
510
|
+
# The result is stored in this IntervalSet.
|
511
|
+
#
|
512
|
+
# IntervalSet[0...1].shift(1) # -> [1...2]
|
513
|
+
#
|
514
|
+
# Note that +shift(0)+ will not be optimized since IntervalSet does
|
515
|
+
# not assume numbers as element type.
|
516
|
+
#
|
517
|
+
# @param amount [Object]
|
518
|
+
# @return [IntervalSet] self.
|
519
|
+
def shift!(amount)
|
520
|
+
ranges = map {|range| range.first + amount...range.last + amount}
|
521
|
+
clear
|
522
|
+
ranges.each {|range| put(range)}
|
523
|
+
update_bounds
|
524
|
+
|
525
|
+
self
|
526
|
+
end
|
527
|
+
|
528
|
+
# Shifts this IntervalSet by the given amount.
|
529
|
+
# The result is stored in a new IntervalSet.
|
530
|
+
#
|
531
|
+
# IntervalSet[0...1].shift!(1) # -> [1...2]
|
532
|
+
#
|
533
|
+
# Note that +shift!(0)+ will not be optimized since IntervalSet does
|
534
|
+
# not assume numbers as element type.
|
535
|
+
#
|
536
|
+
# @param amount [Object]
|
537
|
+
# @return [IntervalSet] a new IntervalSet shifted by +amount+.
|
538
|
+
def shift(amount)
|
539
|
+
clone.shift!(amount)
|
540
|
+
end
|
541
|
+
|
542
|
+
# Buffers this IntervalSet by adding a left and right margin to each range.
|
543
|
+
# The result is stored in this IntervalSet.
|
544
|
+
#
|
545
|
+
# IntervalSet[1...2].buffer!(1, 2) # -> [0...4]
|
546
|
+
#
|
547
|
+
# # negative values will shrink the ranges
|
548
|
+
# IntervalSet[0...4].buffer!(-1, -2) # -> [1...2]
|
549
|
+
# IntervalSet[1...2].buffer!(-0.5, -0.5) # -> []
|
550
|
+
#
|
551
|
+
# @param left [Object] margin added to the left side of each range.
|
552
|
+
# @param right [Object] margin added to the right side of each range.
|
553
|
+
# @return [IntervalSet] self.
|
554
|
+
def buffer!(left, right)
|
555
|
+
ranges = map do |range|
|
556
|
+
range.first - left...range.last + right
|
557
|
+
end.select do |range|
|
558
|
+
range.first < range.last
|
559
|
+
end
|
560
|
+
|
561
|
+
clear
|
562
|
+
ranges.each {|r| add_range(r)}
|
563
|
+
|
564
|
+
self
|
565
|
+
end
|
566
|
+
|
567
|
+
# Buffers this IntervalSet by adding a left and right margin to each range.
|
568
|
+
# The result is stored in a new IntervalSet.
|
569
|
+
#
|
570
|
+
# IntervalSet[1...2].buffer(1, 2) # -> [0...4]
|
571
|
+
#
|
572
|
+
# # negative values will shrink the ranges
|
573
|
+
# IntervalSet[0...4].buffer(-1, -2) # -> [1...2]
|
574
|
+
# IntervalSet[1...2].buffer(-0.5, -0.5) # -> []
|
575
|
+
#
|
576
|
+
# @param left [Object] margin added to the left side of each range.
|
577
|
+
# @param right [Object] margin added to the right side of each range.
|
578
|
+
# @return [IntervalSet] a new IntervalSet containing the buffered ranges.
|
579
|
+
def buffer(left, right)
|
580
|
+
clone.buffer!(left, right)
|
581
|
+
end
|
582
|
+
|
583
|
+
# Removes all elements from this IntervalSet.
|
584
|
+
# @return [IntervalSet] self.
|
585
|
+
def clear
|
586
|
+
@range_map.clear
|
587
|
+
@min = nil
|
588
|
+
@max = nil
|
589
|
+
|
590
|
+
self
|
591
|
+
end
|
592
|
+
|
593
|
+
# Iterates over all ranges of this set in ascending order.
|
594
|
+
# @yield all ranges.
|
595
|
+
# @yieldparam [Range] range.
|
596
|
+
# @return [void]
|
597
|
+
def each
|
598
|
+
@range_map.each_node {|node| yield node.value}
|
599
|
+
end
|
600
|
+
|
601
|
+
# Returns a new IntervalSet instance containing all ranges of this IntervalSet.
|
602
|
+
# @return [IntervalSet] the clone.
|
603
|
+
def clone
|
604
|
+
IntervalSet.new.copy(self)
|
605
|
+
end
|
606
|
+
|
607
|
+
# Replaces the content of this IntervalSet by the content of the given IntervalSet.
|
608
|
+
# @param interval_set [IntervalSet] the other IntervalSet to be copied
|
609
|
+
# @return [IntervalSet] self.
|
610
|
+
def copy(interval_set)
|
611
|
+
clear
|
612
|
+
interval_set.each {|range| put(range)}
|
613
|
+
@min = interval_set.min
|
614
|
+
@max = interval_set.max
|
615
|
+
|
616
|
+
self
|
617
|
+
end
|
618
|
+
|
619
|
+
# Returns a String representation of this IntervalSet.
|
620
|
+
#
|
621
|
+
# IntervalSet[].to_s # -> "[]"
|
622
|
+
# IntervalSet[0...1].to_s # -> "[0...1]"
|
623
|
+
# IntervalSet[0...1, 2...3].to_s # -> "[0...1, 2...3]"
|
624
|
+
#
|
625
|
+
# @return [String] the String representation.
|
626
|
+
def to_s
|
627
|
+
string_io = StringIO.new
|
628
|
+
|
629
|
+
last_index = count - 1
|
630
|
+
|
631
|
+
string_io << '['
|
632
|
+
each_with_index do |range, i|
|
633
|
+
string_io << range
|
634
|
+
string_io << ', ' if i < last_index
|
635
|
+
end
|
636
|
+
string_io << ']'
|
637
|
+
|
638
|
+
string_io.string
|
639
|
+
end
|
640
|
+
|
641
|
+
alias_method :inspect, :to_s
|
642
|
+
|
643
|
+
protected
|
644
|
+
|
645
|
+
def range_map
|
646
|
+
@range_map
|
647
|
+
end
|
648
|
+
|
649
|
+
def put(range)
|
650
|
+
@range_map.put(range.first, IntervalSet.normalize_range(range))
|
651
|
+
end
|
652
|
+
|
653
|
+
def put_and_update_bounds(range)
|
654
|
+
put(range)
|
655
|
+
|
656
|
+
if @min.nil? && @max.nil?
|
657
|
+
@min = range.first
|
658
|
+
@max = range.last
|
659
|
+
else
|
660
|
+
@min = [range.first, @min].min
|
661
|
+
@max = [range.last, @max].max
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
def superset_range?(range)
|
666
|
+
return true if IntervalSet.range_empty?(range)
|
667
|
+
return false if empty? || !bounds_intersected_by?(range)
|
668
|
+
|
669
|
+
# left.min <= range.first
|
670
|
+
left_entry = @range_map.floor_entry(range.first)
|
671
|
+
|
672
|
+
# left.max >= range.last
|
673
|
+
!left_entry.nil? && left_entry.value.last >= range.last
|
674
|
+
end
|
675
|
+
|
676
|
+
def superset_interval_set?(interval_set)
|
677
|
+
return true if interval_set == self || interval_set.empty?
|
678
|
+
return false if empty? || !interval_set.bounds_intersected_by?(bounds)
|
679
|
+
|
680
|
+
interval_set.all? {|range| superset_range?(range)}
|
681
|
+
end
|
682
|
+
|
683
|
+
def subset_range?(range)
|
684
|
+
return true if empty?
|
685
|
+
return false if IntervalSet.range_empty?(range)
|
686
|
+
|
687
|
+
empty? || (range.first <= min && range.last >= max)
|
688
|
+
end
|
689
|
+
|
690
|
+
def intersect_range?(range)
|
691
|
+
return false unless bounds_intersected_by?(range)
|
692
|
+
|
693
|
+
# left.min < range.last
|
694
|
+
left_entry = @range_map.lower_entry(range.last)
|
695
|
+
|
696
|
+
# left.max > range.first
|
697
|
+
!left_entry.nil? && left_entry.value.last > range.first
|
698
|
+
end
|
699
|
+
|
700
|
+
def intersect_interval_set?(interval_set)
|
701
|
+
return false if empty? || !bounds_intersected_by?(interval_set.bounds)
|
702
|
+
|
703
|
+
sub_set(interval_set.bounds).any? {|range| intersect_range?(range)}
|
704
|
+
end
|
705
|
+
|
706
|
+
def eql_range?(range)
|
707
|
+
return true if empty? && IntervalSet.range_empty?(range)
|
708
|
+
|
709
|
+
count == 1 && bounds == range
|
710
|
+
end
|
711
|
+
|
712
|
+
def sub_set(range)
|
713
|
+
# left.min < range.first
|
714
|
+
left_entry = @range_map.lower_entry(range.first)
|
715
|
+
|
716
|
+
# left.max > range.first
|
717
|
+
include_left = !left_entry.nil? && left_entry.value.last > range.first
|
718
|
+
|
719
|
+
bound_min = include_left ? left_entry.value.first : range.first
|
720
|
+
sub_map = @range_map.sub_map(bound_min, range.last)
|
721
|
+
|
722
|
+
IntervalSet.new(sub_map)
|
723
|
+
end
|
724
|
+
|
725
|
+
def head_set(value)
|
726
|
+
head_map = @range_map.head_map(value)
|
727
|
+
|
728
|
+
IntervalSet.new(head_map)
|
729
|
+
end
|
730
|
+
|
731
|
+
def tail_set(value)
|
732
|
+
# left.min < value
|
733
|
+
left_entry = @range_map.lower_entry(value)
|
734
|
+
|
735
|
+
# left.max > value
|
736
|
+
include_left = !left_entry.nil? && left_entry.value.last > value
|
737
|
+
|
738
|
+
bound_min = include_left ? left_entry.value.first : value
|
739
|
+
tail_map = @range_map.tail_map(bound_min)
|
740
|
+
|
741
|
+
IntervalSet.new(tail_map)
|
742
|
+
end
|
743
|
+
|
744
|
+
def add_range(range)
|
745
|
+
# ignore empty or reversed ranges
|
746
|
+
return self if IntervalSet.range_empty?(range)
|
747
|
+
|
748
|
+
# short cut
|
749
|
+
unless bounds_intersected_or_touched_by?(range)
|
750
|
+
put_and_update_bounds(range)
|
751
|
+
return self
|
752
|
+
end
|
753
|
+
|
754
|
+
# short cut
|
755
|
+
if subset_range?(range)
|
756
|
+
clear
|
757
|
+
put_and_update_bounds(range)
|
758
|
+
return self
|
759
|
+
end
|
760
|
+
|
761
|
+
# range.first <= core.min <= range.last
|
762
|
+
core = @range_map.sub_map(range.first, true, range.last, true)
|
763
|
+
|
764
|
+
# short cut if range already included
|
765
|
+
if !core.empty? && core.first_entry == core.last_entry
|
766
|
+
core_range = core.first_entry.value
|
767
|
+
|
768
|
+
return self if core_range.first == range.first && core_range.last == range.last
|
769
|
+
end
|
770
|
+
|
771
|
+
# left.min < range.first
|
772
|
+
left_entry = @range_map.lower_entry(range.first)
|
773
|
+
# right.min <= range.last
|
774
|
+
right_entry = core.empty? ? left_entry : core.last_entry
|
775
|
+
|
776
|
+
# determine boundaries
|
777
|
+
|
778
|
+
# left.max >= range.first
|
779
|
+
include_left = !left_entry.nil? && left_entry.value.last >= range.first
|
780
|
+
# right.max > range.last
|
781
|
+
include_right = !right_entry.nil? && right_entry.value.last > range.last
|
782
|
+
|
783
|
+
left_boundary = include_left ? left_entry.key : range.first
|
784
|
+
right_boundary = include_right ? right_entry.value.last : range.last
|
785
|
+
|
786
|
+
@range_map.remove(left_boundary) if include_left
|
787
|
+
|
788
|
+
core.keys.each {|key| @range_map.remove(key)}
|
789
|
+
|
790
|
+
# add range
|
791
|
+
|
792
|
+
if !include_left && !include_right
|
793
|
+
put_and_update_bounds(range)
|
794
|
+
else
|
795
|
+
put_and_update_bounds(left_boundary...right_boundary)
|
796
|
+
end
|
797
|
+
|
798
|
+
self
|
799
|
+
end
|
800
|
+
|
801
|
+
def add_interval_set(interval_set)
|
802
|
+
return self if interval_set == self || interval_set.empty?
|
803
|
+
|
804
|
+
interval_set.each {|range| add_range(range)}
|
805
|
+
|
806
|
+
self
|
807
|
+
end
|
808
|
+
|
809
|
+
def remove_range(range)
|
810
|
+
return self unless bounds_intersected_by?(range)
|
811
|
+
|
812
|
+
# range.first <= core.min <= range.last
|
813
|
+
core = @range_map.sub_map(range.first, true, range.last, false)
|
814
|
+
|
815
|
+
# left.min < range.first
|
816
|
+
left_entry = @range_map.lower_entry(range.first)
|
817
|
+
# right.min < range.last
|
818
|
+
right_entry = core.empty? ? left_entry : core.last_entry
|
819
|
+
|
820
|
+
# left.max > range.to
|
821
|
+
include_left = !left_entry.nil? && left_entry.value.last > range.first
|
822
|
+
# right.max > range.last
|
823
|
+
include_right = !right_entry.nil? && right_entry.value.last > range.last
|
824
|
+
|
825
|
+
core.keys.each {|key| @range_map.remove(key)}
|
826
|
+
|
827
|
+
# right first since right might be same as left
|
828
|
+
put(range.last...right_entry.value.last) if include_right
|
829
|
+
put(left_entry.key...range.first) if include_left
|
830
|
+
update_bounds
|
831
|
+
|
832
|
+
self
|
833
|
+
end
|
834
|
+
|
835
|
+
def remove_interval_set(interval_set)
|
836
|
+
if interval_set == self
|
837
|
+
clear
|
838
|
+
else
|
839
|
+
interval_set.each {|range| remove_range(range)}
|
840
|
+
end
|
841
|
+
|
842
|
+
self
|
843
|
+
end
|
844
|
+
|
845
|
+
def intersect_range(range)
|
846
|
+
unless bounds_intersected_by?(range)
|
847
|
+
clear
|
848
|
+
return self
|
849
|
+
end
|
850
|
+
|
851
|
+
return self if subset_range?(range)
|
852
|
+
|
853
|
+
# left_map.min < range.first
|
854
|
+
left_map = @range_map.head_map(range.first, false)
|
855
|
+
# right_map.min >= range.last
|
856
|
+
right_map = @range_map.tail_map(range.last, true)
|
857
|
+
|
858
|
+
# left.min < range.first
|
859
|
+
left_entry = left_map.last_entry
|
860
|
+
# right.min < range.last
|
861
|
+
right_entry = @range_map.lower_entry(range.last)
|
862
|
+
|
863
|
+
# left.max > range.first
|
864
|
+
include_left = !left_entry.nil? && left_entry.value.last > range.first
|
865
|
+
# right.max > right.max
|
866
|
+
include_right = !right_entry.nil? && right_entry.value.last > range.last
|
867
|
+
|
868
|
+
left_map.keys.each {|key| @range_map.remove(key)}
|
869
|
+
right_map.keys.each {|key| @range_map.remove(key)}
|
870
|
+
|
871
|
+
put(range.first...[left_entry.value.last, range.last].min) if include_left
|
872
|
+
put([right_entry.key, range.first].max...range.last) if include_right
|
873
|
+
update_bounds
|
874
|
+
|
875
|
+
self
|
876
|
+
end
|
877
|
+
|
878
|
+
def intersect_interval_set(interval_set)
|
879
|
+
return self if interval_set == self
|
880
|
+
|
881
|
+
if interval_set.empty? || !bounds_intersected_by?(interval_set.bounds)
|
882
|
+
clear
|
883
|
+
return self
|
884
|
+
end
|
885
|
+
|
886
|
+
intersection = interval_set.sub_set(bounds).map do |range|
|
887
|
+
IntervalSet.new.tap do |interval_set_item|
|
888
|
+
interval_set_item.add_interval_set(sub_set(range))
|
889
|
+
interval_set_item.intersect_range(range)
|
890
|
+
end
|
891
|
+
end.reduce do |acc, interval_set_item|
|
892
|
+
acc.add_interval_set(interval_set_item); acc
|
893
|
+
end
|
894
|
+
|
895
|
+
@range_map = intersection.range_map
|
896
|
+
@min = intersection.min
|
897
|
+
@max = intersection.max
|
898
|
+
|
899
|
+
self
|
900
|
+
end
|
901
|
+
|
902
|
+
def union_range(range)
|
903
|
+
new_interval_set = IntervalSet.new
|
904
|
+
new_interval_set.add_interval_set(self) unless subset_range?(range)
|
905
|
+
new_interval_set.add_range(range)
|
906
|
+
end
|
907
|
+
|
908
|
+
def union_interval_set(interval_set)
|
909
|
+
new_interval_set = clone
|
910
|
+
new_interval_set.add_interval_set(interval_set)
|
911
|
+
end
|
912
|
+
|
913
|
+
def difference_range(range)
|
914
|
+
new_interval_set = IntervalSet.new
|
915
|
+
|
916
|
+
return new_interval_set if subset_range?(range)
|
917
|
+
return new_interval_set.copy(self) unless bounds_intersected_by?(range)
|
918
|
+
|
919
|
+
unless IntervalSet.range_empty?(range)
|
920
|
+
new_interval_set.add_interval_set(head_set(range.first))
|
921
|
+
new_interval_set.add_interval_set(tail_set(range.last))
|
922
|
+
new_interval_set.remove_range(range)
|
923
|
+
end
|
924
|
+
end
|
925
|
+
|
926
|
+
def difference_interval_set(interval_set)
|
927
|
+
new_interval_set = IntervalSet.new
|
928
|
+
|
929
|
+
return new_interval_set if interval_set == self || empty?
|
930
|
+
|
931
|
+
new_interval_set.copy(self)
|
932
|
+
new_interval_set.remove_interval_set(interval_set) if !interval_set.empty? && bounds_intersected_by?(interval_set.bounds)
|
933
|
+
new_interval_set
|
934
|
+
end
|
935
|
+
|
936
|
+
def intersection_range(range)
|
937
|
+
new_interval_set = IntervalSet.new
|
938
|
+
|
939
|
+
return new_interval_set unless bounds_intersected_by?(range)
|
940
|
+
return new_interval_set.copy(self) if subset_range?(range)
|
941
|
+
|
942
|
+
new_interval_set.add(sub_set(range))
|
943
|
+
new_interval_set.intersect_range(range)
|
944
|
+
end
|
945
|
+
|
946
|
+
def intersection_interval_set(interval_set)
|
947
|
+
new_interval_set = IntervalSet.new
|
948
|
+
|
949
|
+
return new_interval_set if interval_set.empty? || !bounds_intersected_by?(interval_set.bounds)
|
950
|
+
|
951
|
+
new_interval_set.add_interval_set(self)
|
952
|
+
new_interval_set.intersect_interval_set(interval_set.sub_set(bounds))
|
953
|
+
end
|
954
|
+
|
955
|
+
def convolve_range!(range)
|
956
|
+
if IntervalSet.range_empty?(range)
|
957
|
+
clear
|
958
|
+
else
|
959
|
+
buffer!(-range.first, range.last)
|
960
|
+
end
|
961
|
+
end
|
962
|
+
|
963
|
+
def convolve_interval_set!(interval_set)
|
964
|
+
interval_sets = interval_set.map {|range| clone.convolve_range!(range)}
|
965
|
+
clear
|
966
|
+
interval_sets.each {|rs| add_interval_set(rs)}
|
967
|
+
|
968
|
+
self
|
969
|
+
end
|
970
|
+
|
971
|
+
private
|
972
|
+
|
973
|
+
def update_bounds
|
974
|
+
if empty?
|
975
|
+
@min = nil
|
976
|
+
@max = nil
|
977
|
+
else
|
978
|
+
@min = @range_map.first_entry.value.first
|
979
|
+
@max = @range_map.last_entry.value.last
|
980
|
+
end
|
981
|
+
end
|
982
|
+
|
983
|
+
def self.range_empty?(range)
|
984
|
+
range.first >= range.last
|
985
|
+
end
|
986
|
+
|
987
|
+
def self.normalize_range(range)
|
988
|
+
range.exclude_end? ? range : range.first...range.last
|
989
|
+
end
|
990
|
+
|
991
|
+
def self.unexpected_object(object)
|
992
|
+
raise ArgumentError.new("unexpected object #{object}")
|
993
|
+
end
|
994
|
+
|
995
|
+
end
|