better_ranges 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c468002e6d99d8b363b9d9d8633e06b38ff87b3a
4
+ data.tar.gz: e3f6c169d6cc951555576ec423f068a2d2dc3fab
5
+ SHA512:
6
+ metadata.gz: bcf7070b2a67d0aafc67552b9f794a15ffc853aee82905d1406cf8bdfeb1655f857cbe992b1f31143fab4ad04b13699f3e7dc9cfa52240362f6a68cafcc95307
7
+ data.tar.gz: 2a248c8da4946747fcbb047006f921736862177e4603992d42a4fd13b3ee9c85096f920e3643457a1ce048d9d503deae26000e586a5a16e50c51afe3ba249d22
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ *.so
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ .ruby-version
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.0
7
+ - ruby-head
8
+ - jruby-18mode
9
+ - jruby-19mode
10
+ - jruby-head
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in better_ranges.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # BetterRanges
2
+
3
+ Adds support for basic set operations to the ruby Range class through the addition of a new SparseRange class.
4
+ A SparseRange supports all the same methods as a Range plus a few more, most importantly minus (-), intersect (&), and union (|).
5
+
6
+ The purpose of this library is efficiency and convenience. The usual way to achieve these functions is by either first converting the ranges to arrays, which can be slow, or by manually comparing their bounds, which is tedious, especially for more than 2 ranges.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'better_ranges'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install better_ranges
21
+
22
+ ## Usage
23
+
24
+ Just `require 'better_ranges'` and you're good to go.
25
+
26
+ ## Examples
27
+
28
+ ```
29
+ (1..20) - (8..12) - 5 => SparseRange(1...5, 6..7, 13..20)
30
+
31
+ (1..20) & (15..25) => SparseRange(15..20)
32
+
33
+ (1..10) | (5..15) => SparseRange(1..15)
34
+
35
+ ```
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'better_ranges/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'better_ranges'
8
+ spec.version = BetterRanges::VERSION
9
+ spec.authors = ['Michael Ziminsky']
10
+ spec.email = ['mgziminsky@gmail.com']
11
+ spec.summary = 'Add support for set operations to the ruby Range class'
12
+ spec.homepage = 'https://github.com/mgziminsky/better_ranges'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.required_ruby_version = '>= 1.8.6'
21
+
22
+ spec.add_development_dependency 'bundler'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'rspec'
25
+ end
@@ -0,0 +1,2 @@
1
+ require 'better_ranges/version'
2
+ require 'better_ranges/range_operators'
@@ -0,0 +1,23 @@
1
+ require 'better_ranges/sparse_range'
2
+
3
+ module BetterRanges
4
+ module RangeOperators
5
+ def |(x)
6
+ SparseRange.new(self, *x)
7
+ end
8
+
9
+ def -(x)
10
+ SparseRange.new(self) - x
11
+ end
12
+
13
+ def &(x)
14
+ SparseRange.new(self) & x
15
+ end
16
+
17
+ alias :+ :|
18
+ end
19
+ end
20
+
21
+ class Range
22
+ include BetterRanges::RangeOperators
23
+ end
@@ -0,0 +1,240 @@
1
+ module BetterRanges
2
+ class SparseRange
3
+ include Enumerable
4
+
5
+ def initialize(*data)
6
+ @ranges = [*data].map! do |x|
7
+ if (x.is_a?(Enumerable))
8
+ if (x.none?)
9
+ return nil
10
+ elsif (x.is_a?(SparseRange))
11
+ return x.ranges.clone
12
+ end
13
+ end
14
+ x
15
+ end
16
+ optimize
17
+ end
18
+
19
+ def each(&block)
20
+ Enumerator.new do |yielder|
21
+ @ranges.each do |r|
22
+ yield_each(r){ |x| yielder.yield x}
23
+ end
24
+ end.each(&block)
25
+ end
26
+
27
+ def step(num = 1, &block)
28
+ i = 0
29
+ Enumerator.new do |yielder|
30
+ @ranges.each do |r|
31
+ yield_each(r) do |x|
32
+ yielder.yield x if ((i % num) == 0)
33
+ i += 1
34
+ end
35
+ end
36
+ end.each(&block)
37
+ end
38
+
39
+ def last
40
+ read_val(@ranges.last).last
41
+ end
42
+
43
+ def |(x)
44
+ SparseRange.new(@ranges, *x)
45
+ end
46
+
47
+ def -(x)
48
+ diff = SparseRange.new
49
+ diff_data = diff.ranges
50
+
51
+ i = 0
52
+ next_val = lambda do
53
+ throw :done unless i < @ranges.length
54
+ v = read_val(@ranges[i])
55
+ i += 1
56
+ v
57
+ end
58
+
59
+ catch(:done) do
60
+ other = (x.is_a?(SparseRange) ? x : SparseRange.new(x)).ranges
61
+
62
+ start, finish = next_val.call
63
+ other.each do |r|
64
+ other_start, other_finish = read_val(r)
65
+
66
+ while (finish < other_start)
67
+ diff_data << write_val(start, finish)
68
+ start, finish = next_val.call
69
+ end
70
+ next if other_finish < start
71
+
72
+ diff_data << (start.succ == other_start ? start : (start...other_start)) if start < other_start
73
+
74
+ start = other_finish.succ
75
+ start, finish = next_val.call if start > finish
76
+ end
77
+ diff_data << write_val(start, finish)
78
+ loop { diff_data << write_val(*next_val.call) }
79
+ end
80
+
81
+ diff
82
+ end
83
+
84
+ def &(x)
85
+ intersect = SparseRange.new
86
+ intersect_data = intersect.ranges
87
+
88
+ i = 0
89
+ next_val = lambda do
90
+ throw :done unless i < @ranges.length
91
+ v = read_val(@ranges[i])
92
+ i += 1
93
+ v
94
+ end
95
+
96
+ catch(:done) do
97
+ other = (x.is_a?(SparseRange) ? x : SparseRange.new(x)).ranges
98
+
99
+ start, finish = next_val.call
100
+ other.each do |r|
101
+ other_start, other_finish = read_val(r)
102
+ start, finish = next_val.call while (finish < other_start)
103
+
104
+ until other_finish < start
105
+ first = [start, other_start].max
106
+ last = [finish, other_finish].min
107
+
108
+ intersect_data << write_val(first, last)
109
+
110
+ start = last.succ
111
+ if start < other_finish
112
+ other_start = start
113
+ start, finish = next_val.call
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ intersect
120
+ end
121
+
122
+ def <<(x)
123
+ @ranges << [*(x.is_a?(SparseRange) ? x.ranges : x)]
124
+ optimize
125
+
126
+ self
127
+ end
128
+
129
+ def inspect
130
+ @ranges.inspect
131
+ end
132
+
133
+ def include?(x)
134
+ @ranges.any? do |r|
135
+ r.is_a?(Range) ? r.include?(x) : x == r
136
+ end
137
+ end
138
+
139
+ def empty?
140
+ @ranges.empty?
141
+ end
142
+
143
+ def size
144
+ @ranges.inject(0){|c, x| c + (x.is_a?(Range) ? x.count : 1)}
145
+ end
146
+
147
+ def ==(x)
148
+ other = (x.is_a?(SparseRange) ? x : SparseRange.new(x)).ranges
149
+ i = 0
150
+ (@ranges.length == other.length) && @ranges.all? do |e|
151
+ o = other[i]
152
+ i += 1
153
+ (e == o) || (read_val(e) == read_val(o))
154
+ end
155
+ end
156
+
157
+ # TODO: Calculate hash without creating the temp array
158
+ def hash
159
+ @ranges.map(&method(:read_val)).hash
160
+ end
161
+
162
+ alias :+ :|
163
+ alias :union :|
164
+
165
+ alias :minus :-
166
+ alias :intersect :&
167
+
168
+ alias :add :<<
169
+
170
+ alias :cover? :include?
171
+ alias :=== :include?
172
+
173
+ alias :eql? :==
174
+
175
+ protected
176
+ attr_reader :ranges
177
+
178
+ def yield_each(e)
179
+ if (e.is_a?(Range))
180
+ e.each{|x| yield x}
181
+ else
182
+ yield e
183
+ end
184
+ end
185
+
186
+ private
187
+
188
+ def read_val(x)
189
+ x.is_a?(Range) ? [x.first, (x.exclude_end? ? x.max : x.last)] : [x, x]
190
+ end
191
+
192
+ def write_val(start, finish)
193
+ (start != finish ? (start..finish) : finish)
194
+ end
195
+
196
+ def optimize
197
+ @ranges.flatten!
198
+ @ranges.compact!
199
+ @ranges.sort!{|a,b| (a <=> b) || (read_val(a) <=> read_val(b))}
200
+
201
+ fixed = []
202
+ start, finish = read_val(@ranges.first)
203
+
204
+ for i in (1...@ranges.length)
205
+ first, last = read_val(@ranges[i])
206
+
207
+ if (finish.succ >= first)
208
+ finish = last if last > finish
209
+ else
210
+ fixed << write_val(start, finish)
211
+ start, finish = first, last
212
+ end
213
+ end
214
+ fixed << write_val(start, finish) if finish
215
+
216
+ @ranges = fixed
217
+ end
218
+ end
219
+ end
220
+
221
+ class Range
222
+ include Comparable
223
+
224
+ def <=>(other)
225
+ comp = nil
226
+ if (other.is_a?(Range) || other.is_a?(BetterRanges::SparseRange))
227
+ comp = (first <=> other.first)
228
+ comp = (last <=> other.last) if comp == 0
229
+ if comp == 0
230
+ comp = -1 if exclude_end? && !other.exclude_end?
231
+ comp = 1 if !exclude_end? && other.exclude_end?
232
+ end
233
+ else
234
+ comp = (first <=> other)
235
+ comp = (last <=> other) if comp == 0
236
+ comp = -1 if comp == 0 && exclude_end?
237
+ end
238
+ comp
239
+ end
240
+ end
@@ -0,0 +1,3 @@
1
+ module BetterRanges
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe BetterRanges do
4
+ it 'should have a version number' do
5
+ BetterRanges::VERSION.should_not be_nil
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ include BetterRanges
3
+
4
+ describe BetterRanges::SparseRange do
5
+ context 'subtraction(#-)' do
6
+ it 'should work' do
7
+ a = SparseRange.new(-20..-10, 1..25, 50..75, 100..200, 300..400, 500..600, 700..800, 900)
8
+ b = SparseRange.new(-5..-1, 5..15, 40..80, 90..125, 150..225, 250..350, 550..650, 750, 850..950, 1000)
9
+
10
+ (a - a).should be_empty
11
+ (a - b).should eq [-20..-10, 1...5, 16..25, 126...150, 351..400, 500...550, 700...750, 751..800]
12
+ end
13
+ end
14
+
15
+ context 'intersection(#&)' do
16
+ a = SparseRange.new(1..10, 20..30, 40..50, 60..80)
17
+ b = SparseRange.new(10..20, 30..40, 65..75)
18
+
19
+ it 'should work' do
20
+ (a & a).should eq a
21
+ (a & b).should eq [10, 20, 30, 40, 65..75]
22
+ end
23
+
24
+ it 'should be commutative' do
25
+ (a & b).should eq(b & a)
26
+ end
27
+ end
28
+
29
+ context 'union(#|)' do
30
+ a = SparseRange.new(1..10, 20..30, 40..50, 60..80)
31
+ b = SparseRange.new(10..20, 30..40, 65..75)
32
+
33
+ it 'should work' do
34
+ (a | b).should eq [1..50, 60..80]
35
+ end
36
+
37
+ it 'should be commutative' do
38
+ (a | b).should eq(b | a)
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'better_ranges'
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: better_ranges
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Ziminsky
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description:
56
+ email:
57
+ - mgziminsky@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - better_ranges.gemspec
70
+ - lib/better_ranges.rb
71
+ - lib/better_ranges/range_operators.rb
72
+ - lib/better_ranges/sparse_range.rb
73
+ - lib/better_ranges/version.rb
74
+ - spec/better_ranges_spec.rb
75
+ - spec/sparse_range_spec.rb
76
+ - spec/spec_helper.rb
77
+ homepage: https://github.com/mgziminsky/better_ranges
78
+ licenses:
79
+ - MIT
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 1.8.6
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.2.2
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Add support for set operations to the ruby Range class
101
+ test_files:
102
+ - spec/better_ranges_spec.rb
103
+ - spec/sparse_range_spec.rb
104
+ - spec/spec_helper.rb