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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f1ca3c6c80f92c3cbbd68f289ffd138c1d988bc33cd506e9eded313b0244504
4
- data.tar.gz: e27a2de532a9b307e1f1ca7cfe8eff18fbe3af7e607f11291449eb22ec66ad42
3
+ metadata.gz: fa2432c646c4156bab9056f7c92820959bb4d856940737835b7b8785cd2dd771
4
+ data.tar.gz: 0e1da8c9c9332444d5a65573be13415d07121ec30d8625ad7e1a5655f3496d41
5
5
  SHA512:
6
- metadata.gz: e3623cbe2b34d20ca94b5a15848fc579a597e5381cee8f2245d5e053ef692e4511821772cc4461d62d7e0ea7bc4cc55613468e5df166d5f56bf68ac91c55b7e4
7
- data.tar.gz: c04934e7ddc717db16902fea4846b3aabf531b1ea2c8e015f8bf754d8d9dac8abf5cdd4b1bf4c6770752fc35ad02ecc5e9969ca35c05b73fffa8302c1847f779
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class MultiRange
4
- VERSION = '2.2.2'
4
+ VERSION = '2.2.3'
5
5
  end
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(&:begin).freeze
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 range.end <= current_range.end
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
- current_range = range.exclude_end? ? current_range.begin...range.end : current_range.begin..range.end
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
- # A workaround for the issue: https://github.com/greensync/interval-tree/issues/17
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 other.end <= range.end
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
- sub_range2_begin = if other.exclude_end?
209
- other.end
210
- else
211
- other.end.is_a?(Float) ? other.end.next_float : other.end + 1
212
- end
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
- sub_range2 = range.exclude_end? ? sub_range2_begin...range.end : sub_range2_begin..range.end
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
- range.begin > range.end || (range.begin == range.end && range.exclude_end?)
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.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-07 00:00:00.000000000 Z
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: