multi_range 2.2.2 → 2.2.3
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/CHANGELOG.md +3 -0
- data/lib/multi_range/version.rb +1 -1
- data/lib/multi_range.rb +36 -22
- data/lib/patches/patch_interval_tree.rb +97 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa2432c646c4156bab9056f7c92820959bb4d856940737835b7b8785cd2dd771
|
4
|
+
data.tar.gz: 0e1da8c9c9332444d5a65573be13415d07121ec30d8625ad7e1a5655f3496d41
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 80e4731b7df2487a3aad29028ff370f833c3d4b993e6aafb14aeeefb9cb505bdfbe7663553907a9725fcf11f626d43d6b30342338eafed40d2ec9c22bcb53363
|
7
|
+
data.tar.gz: f70e7893acf075f2a74da10692206c71a7785812d086a75ccfa533b526eeb47f199585c38e4b82e3f24a4b9ecbeb2d490b0ab59e9424981e3b4a0db4d49f1a5a
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
## Change Log
|
2
2
|
|
3
|
+
### [v2.2.2](https://github.com/khiav223577/multi_range/compare/v2.2.1...v2.2.2) 2023/12/07
|
4
|
+
- [#40](https://github.com/khiav223577/multi_range/pull/40) Enhance: do not need to flatten ranges when ranges is not array (@khiav223577)
|
5
|
+
|
3
6
|
### [v2.2.1](https://github.com/khiav223577/multi_range/compare/v2.2.0...v2.2.1) 2023/10/28
|
4
7
|
- [#35](https://github.com/khiav223577/multi_range/pull/35) Fix: intersection with excluded empty range contains extra element (@khiav223577)
|
5
8
|
|
data/lib/multi_range/version.rb
CHANGED
data/lib/multi_range.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'multi_range/version'
|
4
4
|
require 'roulette-wheel-selection'
|
5
5
|
require 'interval_tree'
|
6
|
+
require_relative 'patches/patch_interval_tree'
|
6
7
|
|
7
8
|
if not Range.method_defined?(:size)
|
8
9
|
warn "Please backports Range#size method to use multi_range gem.\n" \
|
@@ -28,7 +29,7 @@ class MultiRange
|
|
28
29
|
@is_float = ranges.is_float?
|
29
30
|
else
|
30
31
|
ranges = [ranges] if !ranges.is_a?(Array)
|
31
|
-
@ranges = ranges.map{|s| s.is_a?(Numeric) ? s..s : s }.sort_by
|
32
|
+
@ranges = ranges.map{|s| s.is_a?(Numeric) ? s..s : s }.sort_by{|s| s.begin || -Float::INFINITY }.freeze
|
32
33
|
@is_float = @ranges.any?{|range| range.begin.is_a?(Float) || range.end.is_a?(Float) }
|
33
34
|
end
|
34
35
|
end
|
@@ -45,10 +46,13 @@ class MultiRange
|
|
45
46
|
|
46
47
|
@ranges.each do |range|
|
47
48
|
next current_range = range if current_range == nil
|
48
|
-
next if
|
49
|
+
next if current_range.end == nil
|
50
|
+
next if range.end && range.end <= current_range.end
|
49
51
|
|
50
52
|
if can_combine?(current_range, range, merge_same_value)
|
51
|
-
|
53
|
+
end_val = range.end
|
54
|
+
end_val = Float::INFINITY if end_val == nil && current_range.begin == nil
|
55
|
+
current_range = range.exclude_end? ? current_range.begin...end_val : current_range.begin..end_val
|
52
56
|
else
|
53
57
|
new_ranges << current_range
|
54
58
|
current_range = range
|
@@ -61,11 +65,10 @@ class MultiRange
|
|
61
65
|
|
62
66
|
def &(other)
|
63
67
|
other_ranges = MultiRange.new(other).merge_overlaps.ranges
|
64
|
-
tree = IntervalTree::Tree.new(other_ranges)
|
68
|
+
tree = IntervalTree::Tree.new(other_ranges){|l, r| r ? (l...(r + 1)) : l...nil }
|
69
|
+
|
65
70
|
intersected_ranges = merge_overlaps.ranges.flat_map do |range|
|
66
|
-
|
67
|
-
query = (range.first == range.last) ? range.first : range
|
68
|
-
matching_ranges_converted_to_exclusive = tree.search(query) || []
|
71
|
+
matching_ranges_converted_to_exclusive = tree.search(range) || []
|
69
72
|
|
70
73
|
# The interval tree converts interval endings to exclusive, so we need to restore the original
|
71
74
|
matching_ranges = matching_ranges_converted_to_exclusive.map do |matching_range_converted_to_exclusive|
|
@@ -96,12 +99,12 @@ class MultiRange
|
|
96
99
|
# when this range is smaller than and not overlaps with `other`
|
97
100
|
# range other
|
98
101
|
# |---------| |---------|
|
99
|
-
next if other.begin > range.end
|
102
|
+
next if other.begin && range.end && other.begin > range.end
|
100
103
|
|
101
104
|
# when this range is larger than and not overlaps with `other`
|
102
105
|
# other range
|
103
106
|
# |---------| |---------|
|
104
|
-
break if other.end < range.begin
|
107
|
+
break if other.end && range.begin && other.end < range.begin
|
105
108
|
|
106
109
|
sub_ranges = possible_sub_ranges_of(range, other)
|
107
110
|
new_ranges[idx + changed_size, 1] = sub_ranges
|
@@ -112,7 +115,8 @@ class MultiRange
|
|
112
115
|
# -------------|
|
113
116
|
# other
|
114
117
|
# ---------|
|
115
|
-
break if
|
118
|
+
break if range.end == nil
|
119
|
+
break if other.end && other.end <= range.end
|
116
120
|
end
|
117
121
|
|
118
122
|
return MultiRange.new(new_ranges)
|
@@ -191,6 +195,8 @@ class MultiRange
|
|
191
195
|
|
192
196
|
# make sure that range1.begin <= range2.begin
|
193
197
|
def can_combine?(range1, range2, merge_same_value)
|
198
|
+
return true if range1.end == nil
|
199
|
+
return true if range2.begin == nil
|
194
200
|
return merge_same_value if range1.end == range2.begin and range1.exclude_end?
|
195
201
|
return range1.end >= range2.begin if @is_float
|
196
202
|
return range1.end + 1 >= range2.begin
|
@@ -203,26 +209,28 @@ class MultiRange
|
|
203
209
|
end
|
204
210
|
|
205
211
|
def possible_sub_ranges_of(range, other)
|
206
|
-
sub_range1 = range.begin...other.begin
|
212
|
+
sub_range1 = range.begin...other.begin if other.begin
|
207
213
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
214
|
+
if other.end
|
215
|
+
sub_range2_begin = if other.exclude_end?
|
216
|
+
other.end
|
217
|
+
else
|
218
|
+
other.end.is_a?(Float) ? other.end.next_float : other.end + 1
|
219
|
+
end
|
213
220
|
|
214
|
-
|
221
|
+
sub_range2 = range.exclude_end? ? sub_range2_begin...range.end : sub_range2_begin..range.end
|
222
|
+
end
|
215
223
|
|
216
224
|
sub_ranges = []
|
217
|
-
sub_ranges << sub_range1 if not empty_range?(sub_range1)
|
218
|
-
sub_ranges << sub_range2 if not empty_range?(sub_range2)
|
225
|
+
sub_ranges << sub_range1 if sub_range1 and not empty_range?(sub_range1)
|
226
|
+
sub_ranges << sub_range2 if sub_range2 and not empty_range?(sub_range2)
|
219
227
|
return sub_ranges
|
220
228
|
end
|
221
229
|
|
222
230
|
def overlaps_with_range?(range)
|
223
231
|
return false if @ranges.empty?
|
224
|
-
return false if range.begin > @ranges.last.end # larger than maximum
|
225
|
-
return false if range.end < @ranges.first.begin # smaller than minimum
|
232
|
+
return false if range.begin && @ranges.last.end && range.begin > @ranges.last.end # larger than maximum
|
233
|
+
return false if range.end && @ranges.first.begin && range.end < @ranges.first.begin # smaller than minimum
|
226
234
|
return true
|
227
235
|
end
|
228
236
|
|
@@ -234,15 +242,21 @@ class MultiRange
|
|
234
242
|
end
|
235
243
|
|
236
244
|
def empty_range?(range)
|
237
|
-
|
245
|
+
return false if range.begin == nil
|
246
|
+
return false if range.end == nil
|
247
|
+
return range.begin > range.end || (range.begin == range.end && range.exclude_end?)
|
238
248
|
end
|
239
249
|
|
240
250
|
def range_with_larger_start(range1, range2)
|
251
|
+
return range2 if range1.begin == nil
|
252
|
+
return range1 if range2.begin == nil
|
241
253
|
return range1 if range1.begin > range2.begin
|
242
254
|
return range2
|
243
255
|
end
|
244
256
|
|
245
257
|
def range_with_smaller_end(range1, range2)
|
258
|
+
return range2 if range1.end == nil
|
259
|
+
return range1 if range2.end == nil
|
246
260
|
return range1 if range1.end < range2.end
|
247
261
|
return range2 if range1.end > range2.end
|
248
262
|
return range1 if range1.exclude_end?
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Patches
|
2
|
+
module PatchIntervalTree
|
3
|
+
module ForTree
|
4
|
+
def divide_intervals(intervals)
|
5
|
+
return nil if intervals.empty?
|
6
|
+
x_center = center(intervals)
|
7
|
+
s_center = []
|
8
|
+
s_left = []
|
9
|
+
s_right = []
|
10
|
+
|
11
|
+
intervals.each do |k|
|
12
|
+
case
|
13
|
+
when k.end && k.end != Float::INFINITY && k.end.to_r < x_center
|
14
|
+
s_left << k
|
15
|
+
when k.begin && k.begin.to_r > x_center
|
16
|
+
s_right << k
|
17
|
+
else
|
18
|
+
s_center << k
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
IntervalTree::Node.new(x_center, s_center, divide_intervals(s_left), divide_intervals(s_right))
|
23
|
+
end
|
24
|
+
|
25
|
+
def search(query, options = {})
|
26
|
+
options = IntervalTree::Tree::DEFAULT_OPTIONS.merge(options)
|
27
|
+
|
28
|
+
return nil unless @top_node
|
29
|
+
|
30
|
+
if query.respond_to?(:first)
|
31
|
+
result = top_node.search(query)
|
32
|
+
options[:unique] ? result.uniq : result
|
33
|
+
else
|
34
|
+
point_search(top_node, query, [], options[:unique])
|
35
|
+
end
|
36
|
+
.sort_by{|x| [x.begin || -Float::INFINITY, x.end || Float::INFINITY] }
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def ensure_exclusive_end(ranges, range_factory)
|
42
|
+
ranges.map do |range|
|
43
|
+
case
|
44
|
+
when !range.respond_to?(:exclude_end?)
|
45
|
+
range
|
46
|
+
when range.exclude_end?
|
47
|
+
range
|
48
|
+
else
|
49
|
+
range_factory.call(range.begin, range.end)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def center(intervals)
|
55
|
+
min_val = intervals.map(&:begin).compact.min
|
56
|
+
max_val = intervals.map(&:end).compact.max
|
57
|
+
return min_val * 2 if max_val == nil
|
58
|
+
return max_val / 2 if min_val == nil
|
59
|
+
return (min_val.to_r + max_val.to_r) / 2
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module ForNode
|
64
|
+
def search_s_center(query)
|
65
|
+
s_center.select do |range|
|
66
|
+
# when this range is smaller than and not overlaps with `query`
|
67
|
+
# range query
|
68
|
+
# |---------| |---------|
|
69
|
+
if query.begin and range.end
|
70
|
+
next false if query.begin > range.end
|
71
|
+
next false if query.begin == range.end and range.exclude_end?
|
72
|
+
end
|
73
|
+
|
74
|
+
# when this range is larger than and not overlaps with `query`
|
75
|
+
# query range
|
76
|
+
# |---------| |---------|
|
77
|
+
if query.end and range.begin
|
78
|
+
next false if query.end < range.begin
|
79
|
+
next false if query.end == range.begin and query.exclude_end?
|
80
|
+
end
|
81
|
+
|
82
|
+
next true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
module IntervalTree
|
90
|
+
class Tree
|
91
|
+
prepend Patches::PatchIntervalTree::ForTree
|
92
|
+
end
|
93
|
+
|
94
|
+
class Node
|
95
|
+
prepend Patches::PatchIntervalTree::ForNode
|
96
|
+
end
|
97
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: multi_range
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.
|
4
|
+
version: 2.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- khiav reoy
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-12-
|
11
|
+
date: 2023-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -121,6 +121,7 @@ files:
|
|
121
121
|
- gemfiles/Gemfile
|
122
122
|
- lib/multi_range.rb
|
123
123
|
- lib/multi_range/version.rb
|
124
|
+
- lib/patches/patch_interval_tree.rb
|
124
125
|
- multi_range.gemspec
|
125
126
|
homepage: https://github.com/khiav223577/multi_range
|
126
127
|
licenses:
|