multi_range 2.2.1 → 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 +7 -1
- data/lib/multi_range/version.rb +1 -1
- data/lib/multi_range.rb +37 -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,6 +1,12 @@
|
|
1
1
|
## Change Log
|
2
2
|
|
3
|
-
### [v2.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
|
+
|
6
|
+
### [v2.2.1](https://github.com/khiav223577/multi_range/compare/v2.2.0...v2.2.1) 2023/10/28
|
7
|
+
- [#35](https://github.com/khiav223577/multi_range/pull/35) Fix: intersection with excluded empty range contains extra element (@khiav223577)
|
8
|
+
|
9
|
+
### [v2.2.0](https://github.com/khiav223577/multi_range/compare/v2.1.1...v2.2.0) 2023/10/21
|
4
10
|
- [#34](https://github.com/khiav223577/multi_range/pull/34) Fix: wrong empty range check which causes some differences to be dropped (@khiav223577)
|
5
11
|
- [#33](https://github.com/khiav223577/multi_range/pull/33) Fix: result should not be empty when intersection with inclusive range with one element (@khiav223577)
|
6
12
|
- [#32](https://github.com/khiav223577/multi_range/pull/32) Drop the support of ruby 2.2 (@khiav223577)
|
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" \
|
@@ -27,7 +28,8 @@ class MultiRange
|
|
27
28
|
@ranges = ranges.ranges
|
28
29
|
@is_float = ranges.is_float?
|
29
30
|
else
|
30
|
-
|
31
|
+
ranges = [ranges] if !ranges.is_a?(Array)
|
32
|
+
@ranges = ranges.map{|s| s.is_a?(Numeric) ? s..s : s }.sort_by{|s| s.begin || -Float::INFINITY }.freeze
|
31
33
|
@is_float = @ranges.any?{|range| range.begin.is_a?(Float) || range.end.is_a?(Float) }
|
32
34
|
end
|
33
35
|
end
|
@@ -44,10 +46,13 @@ class MultiRange
|
|
44
46
|
|
45
47
|
@ranges.each do |range|
|
46
48
|
next current_range = range if current_range == nil
|
47
|
-
next if
|
49
|
+
next if current_range.end == nil
|
50
|
+
next if range.end && range.end <= current_range.end
|
48
51
|
|
49
52
|
if can_combine?(current_range, range, merge_same_value)
|
50
|
-
|
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
|
51
56
|
else
|
52
57
|
new_ranges << current_range
|
53
58
|
current_range = range
|
@@ -60,11 +65,10 @@ class MultiRange
|
|
60
65
|
|
61
66
|
def &(other)
|
62
67
|
other_ranges = MultiRange.new(other).merge_overlaps.ranges
|
63
|
-
tree = IntervalTree::Tree.new(other_ranges)
|
68
|
+
tree = IntervalTree::Tree.new(other_ranges){|l, r| r ? (l...(r + 1)) : l...nil }
|
69
|
+
|
64
70
|
intersected_ranges = merge_overlaps.ranges.flat_map do |range|
|
65
|
-
|
66
|
-
query = (range.first == range.last) ? range.first : range
|
67
|
-
matching_ranges_converted_to_exclusive = tree.search(query) || []
|
71
|
+
matching_ranges_converted_to_exclusive = tree.search(range) || []
|
68
72
|
|
69
73
|
# The interval tree converts interval endings to exclusive, so we need to restore the original
|
70
74
|
matching_ranges = matching_ranges_converted_to_exclusive.map do |matching_range_converted_to_exclusive|
|
@@ -95,12 +99,12 @@ class MultiRange
|
|
95
99
|
# when this range is smaller than and not overlaps with `other`
|
96
100
|
# range other
|
97
101
|
# |---------| |---------|
|
98
|
-
next if other.begin > range.end
|
102
|
+
next if other.begin && range.end && other.begin > range.end
|
99
103
|
|
100
104
|
# when this range is larger than and not overlaps with `other`
|
101
105
|
# other range
|
102
106
|
# |---------| |---------|
|
103
|
-
break if other.end < range.begin
|
107
|
+
break if other.end && range.begin && other.end < range.begin
|
104
108
|
|
105
109
|
sub_ranges = possible_sub_ranges_of(range, other)
|
106
110
|
new_ranges[idx + changed_size, 1] = sub_ranges
|
@@ -111,7 +115,8 @@ class MultiRange
|
|
111
115
|
# -------------|
|
112
116
|
# other
|
113
117
|
# ---------|
|
114
|
-
break if
|
118
|
+
break if range.end == nil
|
119
|
+
break if other.end && other.end <= range.end
|
115
120
|
end
|
116
121
|
|
117
122
|
return MultiRange.new(new_ranges)
|
@@ -190,6 +195,8 @@ class MultiRange
|
|
190
195
|
|
191
196
|
# make sure that range1.begin <= range2.begin
|
192
197
|
def can_combine?(range1, range2, merge_same_value)
|
198
|
+
return true if range1.end == nil
|
199
|
+
return true if range2.begin == nil
|
193
200
|
return merge_same_value if range1.end == range2.begin and range1.exclude_end?
|
194
201
|
return range1.end >= range2.begin if @is_float
|
195
202
|
return range1.end + 1 >= range2.begin
|
@@ -202,26 +209,28 @@ class MultiRange
|
|
202
209
|
end
|
203
210
|
|
204
211
|
def possible_sub_ranges_of(range, other)
|
205
|
-
sub_range1 = range.begin...other.begin
|
212
|
+
sub_range1 = range.begin...other.begin if other.begin
|
206
213
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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
|
212
220
|
|
213
|
-
|
221
|
+
sub_range2 = range.exclude_end? ? sub_range2_begin...range.end : sub_range2_begin..range.end
|
222
|
+
end
|
214
223
|
|
215
224
|
sub_ranges = []
|
216
|
-
sub_ranges << sub_range1 if not empty_range?(sub_range1)
|
217
|
-
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)
|
218
227
|
return sub_ranges
|
219
228
|
end
|
220
229
|
|
221
230
|
def overlaps_with_range?(range)
|
222
231
|
return false if @ranges.empty?
|
223
|
-
return false if range.begin > @ranges.last.end # larger than maximum
|
224
|
-
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
|
225
234
|
return true
|
226
235
|
end
|
227
236
|
|
@@ -233,15 +242,21 @@ class MultiRange
|
|
233
242
|
end
|
234
243
|
|
235
244
|
def empty_range?(range)
|
236
|
-
|
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?)
|
237
248
|
end
|
238
249
|
|
239
250
|
def range_with_larger_start(range1, range2)
|
251
|
+
return range2 if range1.begin == nil
|
252
|
+
return range1 if range2.begin == nil
|
240
253
|
return range1 if range1.begin > range2.begin
|
241
254
|
return range2
|
242
255
|
end
|
243
256
|
|
244
257
|
def range_with_smaller_end(range1, range2)
|
258
|
+
return range2 if range1.end == nil
|
259
|
+
return range1 if range2.end == nil
|
245
260
|
return range1 if range1.end < range2.end
|
246
261
|
return range2 if range1.end > range2.end
|
247
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-
|
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:
|