multi_range 2.1.0 → 2.2.0

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: 5f5131aeac614c26aaf827649d945657844d9275943b2dd7b1be8658439c22be
4
- data.tar.gz: 9c28e90964b54c7f8b9a0fd6bf2bce56879075d88f39ab6c329ef9b708a6fc74
3
+ metadata.gz: d15ee2b69e3bd9f58929f35e5e85107e873739b6ecd9499095262e5463b8c733
4
+ data.tar.gz: f641524f185a2f759c17cd85a640ac00d825363f9d62b71e1ac2348a878a6717
5
5
  SHA512:
6
- metadata.gz: 3b316d23ce448b02cddaea5965a8ca87162797a6242383b18b4fdb39bb67eb6e56cf511a639d1ab753caa09278ef452e9e840f8e0e47bf7e5768f8e6dd41f837
7
- data.tar.gz: f1f94bae043fde50aef3e057deed543cfd83f85515d689ee7b4b81bbdba3bfda7ef3e5bdca2242cfc82724c277bc246681e58b8cfb47864c523c2a380a9f259d
6
+ metadata.gz: f8fcafe38be61f3a971578e4f35bae4751f12e15b550531e7867efba0b982bafee5fecaba53674de6db66d99e6355f65b2a751387eb63668b306d17635b9a01f
7
+ data.tar.gz: 818da2c0e7c4e3ce7d07764de88325ffa9bcd3e574d0a73bc46af46b8f7accc3273638ec4704224c5600d087539464b0ca5547907f861bf9eb3aba202e9fb708
@@ -20,8 +20,7 @@ jobs:
20
20
  fail-fast: false
21
21
  matrix:
22
22
  ruby:
23
- - 2.0
24
- - 2.2
23
+ - 2.3
25
24
  - 2.6
26
25
  - 2.7
27
26
  gemfile:
data/.gitignore CHANGED
File without changes
data/.rubocop.yml CHANGED
File without changes
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## Change Log
2
2
 
3
+ ### [v2.1.1](https://github.com/khiav223577/multi_range/compare/v2.1.0...v2.1.1) 2021/08/07
4
+ - [#26](https://github.com/khiav223577/multi_range/pull/26) Fix: unexpected float value when sample an one-element range (@khiav223577)
5
+
6
+ ### [v2.1.0](https://github.com/khiav223577/multi_range/compare/v2.0.0...v2.1.0) 2021/03/17
7
+ - [#24](https://github.com/khiav223577/multi_range/pull/24) Avoid empty sub ranges (@GerritSe)
8
+ - [#25](https://github.com/khiav223577/multi_range/pull/25) Do not publish code coverage for PRs from forks (@khiav223577)
9
+ - [#22](https://github.com/khiav223577/multi_range/pull/22) Migrating from Travis CI to GitHub Actions (@khiav223577)
10
+ - [#21](https://github.com/khiav223577/multi_range/pull/21) Fix: test files should not be included in coverage (@khiav223577)
11
+
3
12
  ### [v2.0.0](https://github.com/khiav223577/multi_range/compare/v1.3.0...v2.0.0) 2020/11/19
4
13
  - [#19](https://github.com/khiav223577/multi_range/pull/19) Implement intersection (@chrisnankervis)
5
14
  - [#20](https://github.com/khiav223577/multi_range/pull/20) Drop support for Ruby 1.8 and 1.9 (@khiav223577)
@@ -33,6 +42,6 @@
33
42
  - [#4](https://github.com/khiav223577/multi_range/pull/4) Add each, map, index_with methods (@khiav223577)
34
43
  - [#3](https://github.com/khiav223577/multi_range/pull/3) Support passing integer as range (@khiav223577)
35
44
 
36
- ### [v0.0.1](https://github.com/khiav223577/multi_range/compare/v0.0.1...v0.0.1) 2020/03/01
45
+ ### v0.0.1 2020/03/01
37
46
  - [#2](https://github.com/khiav223577/multi_range/pull/2) Support Ruby 1.8.7 (@khiav223577)
38
47
  - [#1](https://github.com/khiav223577/multi_range/pull/1) Implement MultiRange (@khiav223577)
data/CODE_OF_CONDUCT.md CHANGED
File without changes
data/LICENSE CHANGED
File without changes
data/README.md CHANGED
@@ -8,9 +8,10 @@
8
8
 
9
9
  ## Supports
10
10
 
11
- - Ruby 2.0 ~ 2.7
11
+ - Ruby 2.3 ~ 2.7
12
12
 
13
13
  For Ruby 1.8.x and 1.9.x, please use multi_range < 2.
14
+ For Ruby 2.0 ~ 2.2, please use multi_range <= 2.1.
14
15
 
15
16
  ## Installation
16
17
 
data/Rakefile CHANGED
File without changes
data/bin/console CHANGED
File without changes
data/bin/setup CHANGED
File without changes
data/gemfiles/Gemfile CHANGED
@@ -3,7 +3,7 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in rails_or.gemspec
4
4
 
5
5
  group :test do
6
- gem 'simplecov', '< 0.18'
6
+ gem 'simplecov', '< 0.18'
7
7
  end
8
8
 
9
9
  gemspec :path => '../'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class MultiRange
4
- VERSION = '2.1.0'
4
+ VERSION = '2.2.0'
5
5
  end
data/lib/multi_range.rb CHANGED
@@ -1,236 +1,242 @@
1
- # frozen_string_literal: true
2
-
3
- require 'multi_range/version'
4
- require 'roulette-wheel-selection'
5
- require 'interval_tree'
6
-
7
- if not Range.method_defined?(:size)
8
- warn "Please backports Range#size method to use multi_range gem.\n" \
9
- "You can use backports gem and add the following lines to your program:\n" \
10
- "require 'backports/1.9.2/float/infinity'\n" \
11
- "require 'backports/2.0.0/range/size'"
12
- end
13
-
14
- if not Enumerable.method_defined?(:to_h)
15
- warn "Please backports Enumerable#to_h method to use multi_range gem.\n" \
16
- "You can use backports gem and add the following lines to your program:\n" \
17
- "require 'backports/2.1.0/enumerable/to_h'"
18
- end
19
-
20
- class MultiRange
21
- INDEX_WITH_DEFAULT = Object.new
22
-
23
- attr_reader :ranges
24
-
25
- def initialize(ranges)
26
- if ranges.is_a? MultiRange
27
- @ranges = ranges.ranges
28
- @is_float = ranges.is_float?
29
- else
30
- @ranges = ranges.map{|s| s.is_a?(Numeric) ? s..s : s }.sort_by(&:begin).freeze
31
- @is_float = @ranges.any?{|range| range.begin.is_a?(Float) || range.end.is_a?(Float) }
32
- end
33
- end
34
-
35
- def is_float?
36
- @is_float
37
- end
38
-
39
- def merge_overlaps(merge_same_value = true)
40
- return MultiRange.new([]) if @ranges.size == 0
41
-
42
- new_ranges = []
43
- current_range = nil
44
-
45
- @ranges.each do |range|
46
- next current_range = range if current_range == nil
47
- next if range.end <= current_range.end
48
-
49
- 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
51
- else
52
- new_ranges << current_range
53
- current_range = range
54
- end
55
- end
56
-
57
- new_ranges << current_range
58
- return MultiRange.new(new_ranges)
59
- end
60
-
61
- def &(other)
62
- other_ranges = MultiRange.new(other).merge_overlaps.ranges
63
- tree = IntervalTree::Tree.new(other_ranges)
64
- intersected_ranges = merge_overlaps.ranges.flat_map do |range|
65
- matching_ranges_converted_to_exclusive = tree.search(range) || []
66
-
67
- # The interval tree converts interval endings to exclusive, so we need to restore the original
68
- matching_ranges = matching_ranges_converted_to_exclusive.map do |matching_range_converted_to_exclusive|
69
- other_ranges.find do |other_range|
70
- # Having merged overlaps in each multi_range, there's no need to check the endings,
71
- # since there will only be one range with each beginning
72
- other_range.begin == matching_range_converted_to_exclusive.begin
73
- end
74
- end
75
-
76
- matching_ranges.map do |matching_range|
77
- intersect_two_ranges(range, matching_range)
78
- end
79
- end
80
- MultiRange.new(intersected_ranges)
81
- end
82
-
83
- alias intersection &
84
-
85
- def -(other)
86
- return difference_with_other_multi_range(other) if other.is_a?(MultiRange)
87
-
88
- new_ranges = @ranges.dup
89
- return MultiRange.new(new_ranges) if not overlaps_with_range?(other)
90
-
91
- changed_size = 0
92
- @ranges.each_with_index do |range, idx|
93
- # when this range is smaller than and not overlaps with `other`
94
- # range other
95
- # |---------| |---------|
96
- next if other.begin > range.end
97
-
98
- # when this range is larger than and not overlaps with `other`
99
- # other range
100
- # |---------| |---------|
101
- break if other.end < range.begin
102
-
103
- sub_ranges = possible_sub_ranges_of(range, other)
104
- new_ranges[idx + changed_size, 1] = sub_ranges
105
- changed_size += sub_ranges.size - 1
106
-
107
- # when the maximum value of this range is larger than that of `other`
108
- # range
109
- # -------------|
110
- # other
111
- # ---------|
112
- break if other.end <= range.end
113
- end
114
-
115
- return MultiRange.new(new_ranges)
116
- end
117
-
118
- alias difference -
119
-
120
- def |(other)
121
- other_ranges = other.is_a?(MultiRange) ? other.ranges : [other]
122
- return MultiRange.new(@ranges + other_ranges).merge_overlaps
123
- end
124
-
125
- alias union |
126
-
127
- def overlaps?(other)
128
- multi_range = merge_overlaps
129
- return multi_range.ranges != (multi_range - other).ranges
130
- end
131
-
132
- def sample
133
- range = RouletteWheelSelection.sample(@ranges.map{|s| [s, s.size] }.to_h)
134
- return nil if range == nil
135
- return rand(range.max - range.min) + range.min
136
- end
137
-
138
- def size
139
- @ranges.inject(0){|sum, v| sum + v.size }
140
- end
141
-
142
- def any?
143
- @ranges.any?
144
- end
145
-
146
- def index_with(default = INDEX_WITH_DEFAULT)
147
- if block_given?
148
- fail ArgumentError, 'wrong number of arguments (given 1, expected 0)' if default != INDEX_WITH_DEFAULT
149
- return map{|s| [s, yield(s)] }.to_h
150
- end
151
-
152
- return to_enum(:index_with){ size } if default == INDEX_WITH_DEFAULT
153
- return map{|s| [s, default] }.to_h
154
- end
155
-
156
- def each
157
- return to_enum(:each){ size } if !block_given?
158
-
159
- ranges.each do |range|
160
- range.each{|s| yield(s) }
161
- end
162
- end
163
-
164
- def map
165
- return to_enum(:map){ size } if !block_given?
166
- return each.map{|s| yield(s) }
167
- end
168
-
169
- def to_a
170
- each.to_a
171
- end
172
-
173
- def min
174
- range = @ranges.first
175
- return range.min if range
176
- end
177
-
178
- def max
179
- range = @ranges.last
180
- return range.max if range
181
- end
182
-
183
- def contain_overlaps?
184
- merge_overlaps(false).ranges != ranges
185
- end
186
-
187
- private
188
-
189
- # make sure that range1.begin <= range2.begin
190
- def can_combine?(range1, range2, merge_same_value)
191
- return merge_same_value if range1.end == range2.begin and range1.exclude_end?
192
- return range1.end >= range2.begin if @is_float
193
- return range1.end + 1 >= range2.begin
194
- end
195
-
196
- def difference_with_other_multi_range(other)
197
- new_multi_range = dup
198
- other.ranges.each{|range| new_multi_range -= range }
199
- return new_multi_range
200
- end
201
-
202
- def possible_sub_ranges_of(range, other)
203
- sub_range1 = range.begin...other.begin
204
-
205
- sub_range2_begin = if other.exclude_end?
206
- other.end
207
- else
208
- other.end + (other.end.is_a?(Float) ? Float::EPSILON : 1)
209
- end
210
-
211
- sub_range2 = range.exclude_end? ? sub_range2_begin...range.end : sub_range2_begin..range.end
212
-
213
- sub_ranges = []
214
- sub_ranges << sub_range1 if sub_range1.begin < sub_range1.end
215
- sub_ranges << sub_range2 if sub_range2.begin < sub_range2.end
216
- return sub_ranges
217
- end
218
-
219
- def overlaps_with_range?(range)
220
- return false if @ranges.empty?
221
- return false if range.begin > @ranges.last.end # larger than maximum
222
- return false if range.end < @ranges.first.begin # smaller than minimum
223
- return true
224
- end
225
-
226
- def intersect_two_ranges(range_a, range_b)
227
- ranges = [range_a, range_b]
228
- start = ranges.map(&:begin).max
229
- finish = ranges.map(&:end).min
230
- if ranges.sort_by { |range| [range.end, range.exclude_end? ? 1 : 0] }.first.exclude_end?
231
- start...finish
232
- else
233
- start..finish
234
- end
235
- end
236
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'multi_range/version'
4
+ require 'roulette-wheel-selection'
5
+ require 'interval_tree'
6
+
7
+ if not Range.method_defined?(:size)
8
+ warn "Please backports Range#size method to use multi_range gem.\n" \
9
+ "You can use backports gem and add the following lines to your program:\n" \
10
+ "require 'backports/1.9.2/float/infinity'\n" \
11
+ "require 'backports/2.0.0/range/size'"
12
+ end
13
+
14
+ if not Enumerable.method_defined?(:to_h)
15
+ warn "Please backports Enumerable#to_h method to use multi_range gem.\n" \
16
+ "You can use backports gem and add the following lines to your program:\n" \
17
+ "require 'backports/2.1.0/enumerable/to_h'"
18
+ end
19
+
20
+ class MultiRange
21
+ INDEX_WITH_DEFAULT = Object.new
22
+
23
+ attr_reader :ranges
24
+
25
+ def initialize(ranges)
26
+ if ranges.is_a? MultiRange
27
+ @ranges = ranges.ranges
28
+ @is_float = ranges.is_float?
29
+ else
30
+ @ranges = ranges.map{|s| s.is_a?(Numeric) ? s..s : s }.sort_by(&:begin).freeze
31
+ @is_float = @ranges.any?{|range| range.begin.is_a?(Float) || range.end.is_a?(Float) }
32
+ end
33
+ end
34
+
35
+ def is_float?
36
+ @is_float
37
+ end
38
+
39
+ def merge_overlaps(merge_same_value = true)
40
+ return MultiRange.new([]) if @ranges.size == 0
41
+
42
+ new_ranges = []
43
+ current_range = nil
44
+
45
+ @ranges.each do |range|
46
+ next current_range = range if current_range == nil
47
+ next if range.end <= current_range.end
48
+
49
+ 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
51
+ else
52
+ new_ranges << current_range
53
+ current_range = range
54
+ end
55
+ end
56
+
57
+ new_ranges << current_range
58
+ return MultiRange.new(new_ranges)
59
+ end
60
+
61
+ def &(other)
62
+ other_ranges = MultiRange.new(other).merge_overlaps.ranges
63
+ tree = IntervalTree::Tree.new(other_ranges)
64
+ 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) || []
68
+
69
+ # The interval tree converts interval endings to exclusive, so we need to restore the original
70
+ matching_ranges = matching_ranges_converted_to_exclusive.map do |matching_range_converted_to_exclusive|
71
+ other_ranges.find do |other_range|
72
+ # Having merged overlaps in each multi_range, there's no need to check the endings,
73
+ # since there will only be one range with each beginning
74
+ other_range.begin == matching_range_converted_to_exclusive.begin
75
+ end
76
+ end
77
+
78
+ matching_ranges.map do |matching_range|
79
+ intersect_two_ranges(range, matching_range)
80
+ end
81
+ end
82
+ MultiRange.new(intersected_ranges)
83
+ end
84
+
85
+ alias intersection &
86
+
87
+ def -(other)
88
+ return difference_with_other_multi_range(other) if other.is_a?(MultiRange)
89
+
90
+ new_ranges = @ranges.dup
91
+ return MultiRange.new(new_ranges) if not overlaps_with_range?(other)
92
+
93
+ changed_size = 0
94
+ @ranges.each_with_index do |range, idx|
95
+ # when this range is smaller than and not overlaps with `other`
96
+ # range other
97
+ # |---------| |---------|
98
+ next if other.begin > range.end
99
+
100
+ # when this range is larger than and not overlaps with `other`
101
+ # other range
102
+ # |---------| |---------|
103
+ break if other.end < range.begin
104
+
105
+ sub_ranges = possible_sub_ranges_of(range, other)
106
+ new_ranges[idx + changed_size, 1] = sub_ranges
107
+ changed_size += sub_ranges.size - 1
108
+
109
+ # when the maximum value of this range is larger than that of `other`
110
+ # range
111
+ # -------------|
112
+ # other
113
+ # ---------|
114
+ break if other.end <= range.end
115
+ end
116
+
117
+ return MultiRange.new(new_ranges)
118
+ end
119
+
120
+ alias difference -
121
+
122
+ def |(other)
123
+ other_ranges = other.is_a?(MultiRange) ? other.ranges : [other]
124
+ return MultiRange.new(@ranges + other_ranges).merge_overlaps
125
+ end
126
+
127
+ alias union |
128
+
129
+ def overlaps?(other)
130
+ multi_range = merge_overlaps
131
+ return multi_range.ranges != (multi_range - other).ranges
132
+ end
133
+
134
+ def sample
135
+ range = RouletteWheelSelection.sample(@ranges.map{|s| [s, s.size] }.to_h)
136
+ return nil if range == nil
137
+ return rand(range)
138
+ end
139
+
140
+ def size
141
+ @ranges.inject(0){|sum, v| sum + v.size }
142
+ end
143
+
144
+ def any?
145
+ @ranges.any?
146
+ end
147
+
148
+ def index_with(default = INDEX_WITH_DEFAULT)
149
+ if block_given?
150
+ fail ArgumentError, 'wrong number of arguments (given 1, expected 0)' if default != INDEX_WITH_DEFAULT
151
+ return map{|s| [s, yield(s)] }.to_h
152
+ end
153
+
154
+ return to_enum(:index_with){ size } if default == INDEX_WITH_DEFAULT
155
+ return map{|s| [s, default] }.to_h
156
+ end
157
+
158
+ def each
159
+ return to_enum(:each){ size } if !block_given?
160
+
161
+ ranges.each do |range|
162
+ range.each{|s| yield(s) }
163
+ end
164
+ end
165
+
166
+ def map
167
+ return to_enum(:map){ size } if !block_given?
168
+ return each.map{|s| yield(s) }
169
+ end
170
+
171
+ def to_a
172
+ each.to_a
173
+ end
174
+
175
+ def min
176
+ range = @ranges.first
177
+ return range.min if range
178
+ end
179
+
180
+ def max
181
+ range = @ranges.last
182
+ return range.max if range
183
+ end
184
+
185
+ def contain_overlaps?
186
+ merge_overlaps(false).ranges != ranges
187
+ end
188
+
189
+ private
190
+
191
+ # make sure that range1.begin <= range2.begin
192
+ def can_combine?(range1, range2, merge_same_value)
193
+ return merge_same_value if range1.end == range2.begin and range1.exclude_end?
194
+ return range1.end >= range2.begin if @is_float
195
+ return range1.end + 1 >= range2.begin
196
+ end
197
+
198
+ def difference_with_other_multi_range(other)
199
+ new_multi_range = dup
200
+ other.ranges.each{|range| new_multi_range -= range }
201
+ return new_multi_range
202
+ end
203
+
204
+ def possible_sub_ranges_of(range, other)
205
+ sub_range1 = range.begin...other.begin
206
+
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
212
+
213
+ sub_range2 = range.exclude_end? ? sub_range2_begin...range.end : sub_range2_begin..range.end
214
+
215
+ sub_ranges = []
216
+ sub_ranges << sub_range1 if not empty_range?(sub_range1)
217
+ sub_ranges << sub_range2 if not empty_range?(sub_range2)
218
+ return sub_ranges
219
+ end
220
+
221
+ def overlaps_with_range?(range)
222
+ 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
225
+ return true
226
+ end
227
+
228
+ def intersect_two_ranges(range_a, range_b)
229
+ ranges = [range_a, range_b]
230
+ start = ranges.map(&:begin).max
231
+ finish = ranges.map(&:end).min
232
+ if ranges.sort_by{|range| [range.end, range.exclude_end? ? 1 : 0] }.first.exclude_end?
233
+ start...finish
234
+ else
235
+ start..finish
236
+ end
237
+ end
238
+
239
+ def empty_range?(range)
240
+ range.begin > range.end || (range.begin == range.end && range.exclude_end?)
241
+ end
242
+ end
data/multi_range.gemspec CHANGED
@@ -39,7 +39,6 @@ Gem::Specification.new do |spec|
39
39
  spec.add_development_dependency 'bundler', '>= 1.17', '< 3.x'
40
40
  spec.add_development_dependency 'rake', '>= 10.5.0'
41
41
  spec.add_development_dependency 'minitest', '~> 5.0'
42
- spec.add_development_dependency 'minitest-color', '~> 0.0.2'
43
42
  spec.add_development_dependency 'backports', '~> 3.15.0'
44
43
 
45
44
  spec.add_dependency 'roulette-wheel-selection', '~> 1.1.1'
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.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - khiav reoy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-17 00:00:00.000000000 Z
11
+ date: 2023-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,20 +58,6 @@ dependencies:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
60
  version: '5.0'
61
- - !ruby/object:Gem::Dependency
62
- name: minitest-color
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: 0.0.2
68
- type: :development
69
- prerelease: false
70
- version_requirements: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: 0.0.2
75
61
  - !ruby/object:Gem::Dependency
76
62
  name: backports
77
63
  requirement: !ruby/object:Gem::Requirement
@@ -160,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
146
  - !ruby/object:Gem::Version
161
147
  version: '0'
162
148
  requirements: []
163
- rubygems_version: 3.0.6
149
+ rubygems_version: 3.2.14
164
150
  signing_key:
165
151
  specification_version: 4
166
152
  summary: Allow you to manipulate a group of ranges.