better_ranges 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +35 -0
- data/Rakefile +6 -0
- data/better_ranges.gemspec +25 -0
- data/lib/better_ranges.rb +2 -0
- data/lib/better_ranges/range_operators.rb +23 -0
- data/lib/better_ranges/sparse_range.rb +240 -0
- data/lib/better_ranges/version.rb +3 -0
- data/spec/better_ranges_spec.rb +7 -0
- data/spec/sparse_range_spec.rb +42 -0
- data/spec/spec_helper.rb +2 -0
- metadata +104 -0
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
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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,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,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,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
|
data/spec/spec_helper.rb
ADDED
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
|