multi_range 1.3.0 → 1.3.1

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.
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
- require 'bundler/gem_tasks'
2
- require 'rake/testtask'
3
-
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << 'test'
6
- t.libs << 'lib'
7
- t.test_files = FileList['test/**/*_test.rb']
8
- end
9
-
10
- task :default => :test
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console CHANGED
@@ -1,14 +1,14 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'bundler/setup'
4
- require 'multi_range'
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require 'irb'
14
- IRB.start
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'multi_range'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup CHANGED
@@ -1,8 +1,8 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install --gemfile=gemfiles/4.2.gemfile
7
-
8
- # Do any other automated setup that you need to do here
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install --gemfile=gemfiles/4.2.gemfile
7
+
8
+ # Do any other automated setup that you need to do here
data/gemfiles/Gemfile CHANGED
@@ -1,9 +1,9 @@
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
- end
8
-
9
- gemspec :path => '../'
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
+ end
8
+
9
+ gemspec :path => '../'
@@ -1,10 +1,10 @@
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 => '../'
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 => '../'
data/lib/multi_range.rb CHANGED
@@ -1,163 +1,174 @@
1
- # frozen_string_literal: true
2
-
3
- require 'multi_range/version'
4
- require 'roulette-wheel-selection'
5
-
6
- if not Range.method_defined?(:size)
7
- warn "Please backports Range#size method to use multi_range gem.\n" \
8
- "You can use backports gem and add the following lines to your program:\n" \
9
- "require 'backports/1.9.2/float/infinity'\n" \
10
- "require 'backports/2.0.0/range/size'"
11
- end
12
-
13
- if not Enumerable.method_defined?(:to_h)
14
- warn "Please backports Enumerable#to_h method to use multi_range gem.\n" \
15
- "You can use backports gem and add the following lines to your program:\n" \
16
- "require 'backports/2.1.0/enumerable/to_h'"
17
- end
18
-
19
- class MultiRange
20
- INDEX_WITH_DEFAULT = Object.new
21
-
22
- attr_reader :ranges
23
-
24
- 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) }
27
- end
28
-
29
- def merge_overlaps(merge_same_value = true)
30
- return MultiRange.new([]) if @ranges.size == 0
31
-
32
- new_ranges = []
33
- current_range = nil
34
-
35
- @ranges.each do |range|
36
- next current_range = range if current_range == nil
37
- next if range.end <= current_range.end
38
-
39
- if can_combine?(current_range, range, merge_same_value)
40
- current_range = range.exclude_end? ? current_range.begin...range.end : current_range.begin..range.end
41
- else
42
- new_ranges << current_range
43
- current_range = range
44
- end
45
- end
46
-
47
- new_ranges << current_range
48
- return MultiRange.new(new_ranges)
49
- end
50
-
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
56
- end
57
-
58
- new_ranges = @ranges.dup
59
-
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 # 小於最小值
63
-
64
- changed_size = 0
65
- @ranges.each_with_index do |range, idx|
66
- next if other.begin > range.end # 大於這個 range
67
- break if other.end < range.begin # 小於這個 range
68
-
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
-
82
- new_ranges[idx + changed_size, 1] = sub_ranges
83
- changed_size += sub_ranges.size - 1
84
- break if other.end <= range.end # 沒有超過一個 range 的範圍
85
- end
86
-
87
- return MultiRange.new(new_ranges)
88
- end
89
-
90
- def |(other)
91
- other_ranges = other.is_a?(MultiRange) ? other.ranges : [other]
92
- return MultiRange.new(@ranges + other_ranges).merge_overlaps
93
- end
94
-
95
- def overlaps?(other)
96
- multi_range = merge_overlaps
97
- return multi_range.ranges != (multi_range - other).ranges
98
- end
99
-
100
- def sample
101
- range = RouletteWheelSelection.sample(@ranges.map{|s| [s, s.size] }.to_h)
102
- return nil if range == nil
103
- return rand(range.max - range.min) + range.min
104
- end
105
-
106
- def size
107
- @ranges.inject(0){|sum, v| sum + v.size }
108
- end
109
-
110
- def any?
111
- @ranges.any?
112
- end
113
-
114
- def index_with(default = INDEX_WITH_DEFAULT)
115
- if block_given?
116
- fail ArgumentError, 'wrong number of arguments (given 1, expected 0)' if default != INDEX_WITH_DEFAULT
117
- return map{|s| [s, yield(s)] }.to_h
118
- end
119
-
120
- return to_enum(:index_with){ size } if default == INDEX_WITH_DEFAULT
121
- return map{|s| [s, default] }.to_h
122
- end
123
-
124
- def each
125
- return to_enum(:each){ size } if !block_given?
126
-
127
- ranges.each do |range|
128
- range.each{|s| yield(s) }
129
- end
130
- end
131
-
132
- def map
133
- return to_enum(:map){ size } if !block_given?
134
- return each.map{|s| yield(s) }
135
- end
136
-
137
- def to_a
138
- each.to_a
139
- end
140
-
141
- def min
142
- range = @ranges.first
143
- return range.min if range
144
- end
145
-
146
- def max
147
- range = @ranges.last
148
- return range.max if range
149
- end
150
-
151
- def contain_overlaps?
152
- merge_overlaps(false).ranges != ranges
153
- end
154
-
155
- private
156
-
157
- # make sure that range1.begin <= range2.begin
158
- def can_combine?(range1, range2, merge_same_value)
159
- return merge_same_value if range1.end == range2.begin and range1.exclude_end?
160
- return range1.end >= range2.begin if @is_float
161
- return range1.end + 1 >= range2.begin
162
- end
163
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'multi_range/version'
4
+ require 'roulette-wheel-selection'
5
+
6
+ if not Range.method_defined?(:size)
7
+ warn "Please backports Range#size method to use multi_range gem.\n" \
8
+ "You can use backports gem and add the following lines to your program:\n" \
9
+ "require 'backports/1.9.2/float/infinity'\n" \
10
+ "require 'backports/2.0.0/range/size'"
11
+ end
12
+
13
+ if not Enumerable.method_defined?(:to_h)
14
+ warn "Please backports Enumerable#to_h method to use multi_range gem.\n" \
15
+ "You can use backports gem and add the following lines to your program:\n" \
16
+ "require 'backports/2.1.0/enumerable/to_h'"
17
+ end
18
+
19
+ class MultiRange
20
+ INDEX_WITH_DEFAULT = Object.new
21
+
22
+ attr_reader :ranges
23
+
24
+ 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) }
27
+ end
28
+
29
+ def merge_overlaps(merge_same_value = true)
30
+ return MultiRange.new([]) if @ranges.size == 0
31
+
32
+ new_ranges = []
33
+ current_range = nil
34
+
35
+ @ranges.each do |range|
36
+ next current_range = range if current_range == nil
37
+ next if range.end <= current_range.end
38
+
39
+ if can_combine?(current_range, range, merge_same_value)
40
+ current_range = range.exclude_end? ? current_range.begin...range.end : current_range.begin..range.end
41
+ else
42
+ new_ranges << current_range
43
+ current_range = range
44
+ end
45
+ end
46
+
47
+ new_ranges << current_range
48
+ return MultiRange.new(new_ranges)
49
+ end
50
+
51
+ def -(other)
52
+ return difference_with_other_multi_range(other) if other.is_a?(MultiRange)
53
+
54
+ new_ranges = @ranges.dup
55
+ return MultiRange.new(new_ranges) if not overlaps_with_range?(other)
56
+
57
+ changed_size = 0
58
+ @ranges.each_with_index do |range, idx|
59
+ next if other.begin > range.end # 大於這個 range
60
+ break if other.end < range.begin # 小於這個 range
61
+
62
+ sub_ranges = possible_sub_ranges_of(range, other)
63
+ new_ranges[idx + changed_size, 1] = sub_ranges
64
+ changed_size += sub_ranges.size - 1
65
+ break if other.end <= range.end # 沒有超過一個 range 的範圍
66
+ end
67
+
68
+ return MultiRange.new(new_ranges)
69
+ end
70
+
71
+ def |(other)
72
+ other_ranges = other.is_a?(MultiRange) ? other.ranges : [other]
73
+ return MultiRange.new(@ranges + other_ranges).merge_overlaps
74
+ end
75
+
76
+ def overlaps?(other)
77
+ multi_range = merge_overlaps
78
+ return multi_range.ranges != (multi_range - other).ranges
79
+ end
80
+
81
+ def sample
82
+ range = RouletteWheelSelection.sample(@ranges.map{|s| [s, s.size] }.to_h)
83
+ return nil if range == nil
84
+ return rand(range.max - range.min + 1) + range.min
85
+ end
86
+
87
+ def size
88
+ @ranges.inject(0){|sum, v| sum + v.size }
89
+ end
90
+
91
+ def any?
92
+ @ranges.any?
93
+ end
94
+
95
+ def index_with(default = INDEX_WITH_DEFAULT)
96
+ if block_given?
97
+ fail ArgumentError, 'wrong number of arguments (given 1, expected 0)' if default != INDEX_WITH_DEFAULT
98
+ return map{|s| [s, yield(s)] }.to_h
99
+ end
100
+
101
+ return to_enum(:index_with){ size } if default == INDEX_WITH_DEFAULT
102
+ return map{|s| [s, default] }.to_h
103
+ end
104
+
105
+ def each
106
+ return to_enum(:each){ size } if !block_given?
107
+
108
+ ranges.each do |range|
109
+ range.each{|s| yield(s) }
110
+ end
111
+ end
112
+
113
+ def map
114
+ return to_enum(:map){ size } if !block_given?
115
+ return each.map{|s| yield(s) }
116
+ end
117
+
118
+ def to_a
119
+ each.to_a
120
+ end
121
+
122
+ def min
123
+ range = @ranges.first
124
+ return range.min if range
125
+ end
126
+
127
+ def max
128
+ range = @ranges.last
129
+ return range.max if range
130
+ end
131
+
132
+ def contain_overlaps?
133
+ merge_overlaps(false).ranges != ranges
134
+ end
135
+
136
+ private
137
+
138
+ # make sure that range1.begin <= range2.begin
139
+ def can_combine?(range1, range2, merge_same_value)
140
+ return merge_same_value if range1.end == range2.begin and range1.exclude_end?
141
+ return range1.end >= range2.begin if @is_float
142
+ return range1.end + 1 >= range2.begin
143
+ end
144
+
145
+ def difference_with_other_multi_range(other)
146
+ new_multi_range = dup
147
+ other.ranges.each{|range| new_multi_range -= range }
148
+ return new_multi_range
149
+ end
150
+
151
+ def possible_sub_ranges_of(range, other)
152
+ sub_range1 = range.begin...other.begin
153
+
154
+ sub_range2_begin = if other.exclude_end?
155
+ other.end
156
+ else
157
+ other.end + (other.end.is_a?(Float) ? Float::EPSILON : 1)
158
+ end
159
+
160
+ sub_range2 = range.exclude_end? ? sub_range2_begin...range.end : sub_range2_begin..range.end
161
+
162
+ sub_ranges = []
163
+ sub_ranges << sub_range1 if sub_range1.begin <= sub_range1.end
164
+ sub_ranges << sub_range2 if sub_range2.begin <= sub_range2.end
165
+ return sub_ranges
166
+ end
167
+
168
+ def overlaps_with_range?(range)
169
+ return false if @ranges.empty?
170
+ return false if range.begin > @ranges.last.end # larger than maxinum
171
+ return false if range.end < @ranges.first.begin # smaller than mininum
172
+ return true
173
+ end
174
+ end
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- class MultiRange
4
- VERSION = '1.3.0'
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ class MultiRange
4
+ VERSION = '1.3.1'
5
+ end