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