multi_range 1.3.0 → 2.0.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: 770406d2ee94a20090941404e6bfde0d3a516554be093ae1eb09aa0f85b19688
4
- data.tar.gz: b1d0e13fb9cd75e2d897d8f2b7e5b9a54ca5c543fcb16c12fb9e35a4cd8717ac
3
+ metadata.gz: 8b6430c0f1e34bd416c5ac86b7427a287b980282604b7220a518feb8eb90e3d3
4
+ data.tar.gz: 01f8ca6da27bc8a960ef531e114cdfc15296fac6af91e4e588642a11b7b56ac7
5
5
  SHA512:
6
- metadata.gz: a0fbb1372f1ccdd9967142fa2ad0e13da8859e3c796c594ea2d53c61646ec98d21a395a0f81f1cc41486fc4b408161ceb2ed45c5fe97aeca701e4688892fd979
7
- data.tar.gz: e41cfe73cc1024d9899a1722215dfbfc2ab0b74bd446e279b1c7c791df162e7c6068332c8ebb01a0594b04d71c7322890fb68c4be148aff46b8a1637a5420717
6
+ metadata.gz: a14a3bee4fece94aa0c39a8fcdd614dfcd94afe771a7075f84e33093cf3a0e5e8990029685f09bc89b13c93d1ce89b82bc527c0f120f43f0238e39e902fd6a28
7
+ data.tar.gz: 513bba26a2dee77e82720214102e9231390954e94cdf36cff610066578179ea67498bfd29576ceb9177362981f34baad2abdddca877f782dc0b47123523e71b0
@@ -4,16 +4,12 @@ env:
4
4
  - CC_TEST_REPORTER_ID=a2bb1313ac63f7ae7a7f13ac962870e3cdb0c345b6c60d2857807cc5153d7c3b
5
5
  language: ruby
6
6
  rvm:
7
+ - 2.0
7
8
  - 2.2
8
9
  - 2.6
9
10
  - 2.7
10
11
  gemfile:
11
12
  - gemfiles/Gemfile
12
- matrix:
13
- include:
14
- - dist: trusty
15
- rvm: 1.8.7
16
- gemfile: gemfiles/ruby_1_8_7.gemfile
17
13
  before_install:
18
14
  - if `ruby -e 'exit(RUBY_VERSION.to_f < 2.7)'`; then
19
15
  gem i rubygems-update -v '< 3' && update_rubygems;
data/README.md CHANGED
@@ -7,7 +7,10 @@
7
7
  [![Test Coverage](https://codeclimate.com/github/khiav223577/multi_range/badges/coverage.svg)](https://codeclimate.com/github/khiav223577/multi_range/coverage)
8
8
 
9
9
  ## Supports
10
- - Ruby 1.8 ~ 2.7
10
+
11
+ - Ruby 2.0 ~ 2.7
12
+
13
+ For Ruby 1.8.x and 1.9.x, please use multi_range < 2.
11
14
 
12
15
  ## Installation
13
16
 
@@ -25,7 +28,7 @@ Or install it yourself as:
25
28
 
26
29
  ## Usage
27
30
 
28
- Allow you to manipulate a group of ranges. Such as merging overlapping ranges, doing ranges union and difference.
31
+ Allow you to manipulate a group of ranges. Such as merging overlapping ranges, doing ranges union, intersection, and difference.
29
32
 
30
33
  ### Sample a number
31
34
  ```rb
@@ -72,6 +75,22 @@ multi_range.ranges
72
75
  # => [1..6, 10..25, 30..30]
73
76
  ```
74
77
 
78
+ ### Intersection ranges
79
+
80
+ ```rb
81
+ multi_range = MultiRange.new([1..5])
82
+ multi_range &= 3..8
83
+ multi_range.ranges
84
+ # => [3..5]
85
+ ```
86
+
87
+ ```rb
88
+ multi_range = MultiRange.new([1..3, 5..10])
89
+ multi_range &= MultiRange.new([2..6, 8..9])
90
+ multi_range.ranges
91
+ # => [2..3, 5..6, 8..9]
92
+ ```
93
+
75
94
  ### Merge overlaps
76
95
  ```rb
77
96
  multi_range = MultiRange.new([1, 2, 4..6, 7, 8..12])
@@ -103,6 +122,23 @@ multi_range.overlaps?(MultiRange.new([6..8, 18..22]))
103
122
  # => true
104
123
  ```
105
124
 
125
+
126
+ ### Check if it contains overlaps
127
+
128
+ ```rb
129
+ MultiRange.new([0..3, 5..10, 20..50]).contain_overlaps?
130
+ # => false
131
+
132
+ MultiRange.new([0...5, 5..10, 20..50]).contain_overlaps?
133
+ # => false
134
+
135
+ MultiRange.new([0..5, 5..10, 20..50]).contain_overlaps?
136
+ # => true
137
+
138
+ MultiRange.new([0...7, 5..10, 20..50]).contain_overlaps?
139
+ # => true
140
+ ```
141
+
106
142
  ### Range-like interface
107
143
 
108
144
  #### each
File without changes
data/bin/setup CHANGED
@@ -3,6 +3,6 @@ set -euo pipefail
3
3
  IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
- bundle install --gemfile=gemfiles/4.2.gemfile
6
+ bundle install --gemfile=gemfiles/Gemfile
7
7
 
8
8
  # Do any other automated setup that you need to do here
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'multi_range/version'
4
4
  require 'roulette-wheel-selection'
5
+ require 'interval_tree'
5
6
 
6
7
  if not Range.method_defined?(:size)
7
8
  warn "Please backports Range#size method to use multi_range gem.\n" \
@@ -22,8 +23,17 @@ class MultiRange
22
23
  attr_reader :ranges
23
24
 
24
25
  def initialize(ranges)
25
- @ranges = ranges.map{|s| s.is_a?(Numeric) ? s..s : s }.sort_by(&:begin).freeze
26
- @is_float = @ranges.any?{|range| range.begin.is_a?(Float) || range.end.is_a?(Float) }
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
27
37
  end
28
38
 
29
39
  def merge_overlaps(merge_same_value = true)
@@ -48,37 +58,41 @@ class MultiRange
48
58
  return MultiRange.new(new_ranges)
49
59
  end
50
60
 
51
- def -(other)
52
- if other.is_a?(MultiRange)
53
- new_multi_range = dup
54
- other.ranges.each{|range| new_multi_range -= range }
55
- return new_multi_range
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 multirange, there's no need to check the endings, since there will only be one range with each beginning
71
+ other_range.begin == matching_range_converted_to_exclusive.begin
72
+ end
73
+ end
74
+
75
+ matching_ranges.map do |matching_range|
76
+ intersect_two_ranges(range, matching_range)
77
+ end
56
78
  end
79
+ MultiRange.new(intersected_ranges)
80
+ end
57
81
 
58
- new_ranges = @ranges.dup
82
+ alias intersection &
59
83
 
60
- return MultiRange.new(new_ranges) if new_ranges.empty?
61
- return MultiRange.new(new_ranges) if other.begin > @ranges.last.end # 大於最大值
62
- return MultiRange.new(new_ranges) if other.end < @ranges.first.begin # 小於最小值
84
+ def -(other)
85
+ return difference_with_other_multi_range(other) if other.is_a?(MultiRange)
86
+
87
+ new_ranges = @ranges.dup
88
+ return MultiRange.new(new_ranges) if not overlaps_with_range?(other)
63
89
 
64
90
  changed_size = 0
65
91
  @ranges.each_with_index do |range, idx|
66
92
  next if other.begin > range.end # 大於這個 range
67
93
  break if other.end < range.begin # 小於這個 range
68
94
 
69
- sub_range1 = range.begin...other.begin
70
-
71
- sub_range2_begin = if other.exclude_end?
72
- other.end
73
- else
74
- other.end + (other.end.is_a?(Float) ? Float::EPSILON : 1)
75
- end
76
- sub_range2 = range.exclude_end? ? sub_range2_begin...range.end : sub_range2_begin..range.end
77
-
78
- sub_ranges = []
79
- sub_ranges << sub_range1 if sub_range1.begin <= sub_range1.end
80
- sub_ranges << sub_range2 if sub_range2.begin <= sub_range2.end
81
-
95
+ sub_ranges = possible_sub_ranges_of(range, other)
82
96
  new_ranges[idx + changed_size, 1] = sub_ranges
83
97
  changed_size += sub_ranges.size - 1
84
98
  break if other.end <= range.end # 沒有超過一個 range 的範圍
@@ -87,11 +101,15 @@ class MultiRange
87
101
  return MultiRange.new(new_ranges)
88
102
  end
89
103
 
104
+ alias difference -
105
+
90
106
  def |(other)
91
107
  other_ranges = other.is_a?(MultiRange) ? other.ranges : [other]
92
108
  return MultiRange.new(@ranges + other_ranges).merge_overlaps
93
109
  end
94
110
 
111
+ alias union |
112
+
95
113
  def overlaps?(other)
96
114
  multi_range = merge_overlaps
97
115
  return multi_range.ranges != (multi_range - other).ranges
@@ -160,4 +178,45 @@ class MultiRange
160
178
  return range1.end >= range2.begin if @is_float
161
179
  return range1.end + 1 >= range2.begin
162
180
  end
181
+
182
+ def difference_with_other_multi_range(other)
183
+ new_multi_range = dup
184
+ other.ranges.each{|range| new_multi_range -= range }
185
+ return new_multi_range
186
+ end
187
+
188
+ def possible_sub_ranges_of(range, other)
189
+ sub_range1 = range.begin...other.begin
190
+
191
+ sub_range2_begin = if other.exclude_end?
192
+ other.end
193
+ else
194
+ other.end + (other.end.is_a?(Float) ? Float::EPSILON : 1)
195
+ end
196
+
197
+ sub_range2 = range.exclude_end? ? sub_range2_begin...range.end : sub_range2_begin..range.end
198
+
199
+ sub_ranges = []
200
+ sub_ranges << sub_range1 if sub_range1.begin <= sub_range1.end
201
+ sub_ranges << sub_range2 if sub_range2.begin <= sub_range2.end
202
+ return sub_ranges
203
+ end
204
+
205
+ def overlaps_with_range?(range)
206
+ return false if @ranges.empty?
207
+ return false if range.begin > @ranges.last.end # larger than maxinum
208
+ return false if range.end < @ranges.first.begin # smaller than mininum
209
+ return true
210
+ end
211
+
212
+ def intersect_two_ranges(range_a, range_b)
213
+ ranges = [range_a, range_b]
214
+ start = ranges.map(&:begin).max
215
+ finish = ranges.map(&:end).min
216
+ if ranges.sort_by { |range| [range.end, range.exclude_end? ? 1 : 0] }.first.exclude_end?
217
+ start...finish
218
+ else
219
+ start..finish
220
+ end
221
+ end
163
222
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class MultiRange
4
- VERSION = '1.3.0'
4
+ VERSION = '2.0.0'
5
5
  end
@@ -34,10 +34,14 @@ Gem::Specification.new do |spec|
34
34
  'bug_tracker_uri' => 'https://github.com/khiav223577/multi_range/issues',
35
35
  }
36
36
 
37
+ spec.required_ruby_version = '>= 2.0'
38
+
37
39
  spec.add_development_dependency 'bundler', '>= 1.17', '< 3.x'
38
40
  spec.add_development_dependency 'rake', '>= 10.5.0'
39
41
  spec.add_development_dependency 'minitest', '~> 5.0'
42
+ spec.add_development_dependency 'minitest-color', '~> 0.0.2'
40
43
  spec.add_development_dependency 'backports', '~> 3.15.0'
41
44
 
42
45
  spec.add_dependency 'roulette-wheel-selection', '~> 1.1.1'
46
+ spec.add_dependency 'fast_interval_tree', '~> 0.2.0'
43
47
  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: 1.3.0
4
+ version: 2.0.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: 2020-10-20 00:00:00.000000000 Z
11
+ date: 2020-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,6 +58,20 @@ 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
61
75
  - !ruby/object:Gem::Dependency
62
76
  name: backports
63
77
  requirement: !ruby/object:Gem::Requirement
@@ -86,6 +100,20 @@ dependencies:
86
100
  - - "~>"
87
101
  - !ruby/object:Gem::Version
88
102
  version: 1.1.1
103
+ - !ruby/object:Gem::Dependency
104
+ name: fast_interval_tree
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 0.2.0
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: 0.2.0
89
117
  description: Allow you to manipulate a group of ranges. Such as merging overlapping
90
118
  ranges, doing ranges union and difference.
91
119
  email:
@@ -105,7 +133,6 @@ files:
105
133
  - bin/console
106
134
  - bin/setup
107
135
  - gemfiles/Gemfile
108
- - gemfiles/ruby_1_8_7.gemfile
109
136
  - lib/multi_range.rb
110
137
  - lib/multi_range/version.rb
111
138
  - multi_range.gemspec
@@ -126,7 +153,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
126
153
  requirements:
127
154
  - - ">="
128
155
  - !ruby/object:Gem::Version
129
- version: '0'
156
+ version: '2.0'
130
157
  required_rubygems_version: !ruby/object:Gem::Requirement
131
158
  requirements:
132
159
  - - ">="
@@ -1,10 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in rails_or.gemspec
4
-
5
- group :test do
6
- gem 'simplecov'
7
- gem 'minitest', '~> 5.11.3'
8
- end
9
-
10
- gemspec :path => '../'