multi_range 2.2.2 → 2.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|