multi_range 2.1.0 → 2.2.0

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