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 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: