multi_range 2.2.1 → 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: 2d67638eab33c3a377488c4fb32c0e33f8385eab6d743d3f37f7476f20f94b97
4
- data.tar.gz: 198971530fc824d45690f92b4c45013a8bd58ddb204e9a84a2971bdda20ed1fb
3
+ metadata.gz: fa2432c646c4156bab9056f7c92820959bb4d856940737835b7b8785cd2dd771
4
+ data.tar.gz: 0e1da8c9c9332444d5a65573be13415d07121ec30d8625ad7e1a5655f3496d41
5
5
  SHA512:
6
- metadata.gz: 752b8eff047ed5b7dded92afbdf612fc0e2f63b119fba141421d07e2983d8fd9aa79914cb60e890414362d6e84497b2737145dc18166299f772a74e74ffb231d
7
- data.tar.gz: 48e47778eaf9066be35983239fdf073e011010ba2555d1a2b4a48d4c507db7e9d419a64503f469a54e16363be61e5e300977f364d02c91f12d56ad3028ef315f
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.0](https://github.com/khiav223577/multi_range/compare/v1.3.2...v2.2.0) 2023/10/21
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class MultiRange
4
- VERSION = '2.2.1'
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" \
@@ -27,7 +28,8 @@ class MultiRange
27
28
  @ranges = ranges.ranges
28
29
  @is_float = ranges.is_float?
29
30
  else
30
- @ranges = ranges.map{|s| s.is_a?(Numeric) ? s..s : s }.sort_by(&:begin).freeze
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 range.end <= current_range.end
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
- 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
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
- # A workaround for the issue: https://github.com/greensync/interval-tree/issues/17
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 other.end <= range.end
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
- sub_range2_begin = if other.exclude_end?
208
- other.end
209
- else
210
- other.end.is_a?(Float) ? other.end.next_float : other.end + 1
211
- 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
212
220
 
213
- 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
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
- 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?)
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.1
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-10-28 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: