composite_range 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in composite_range.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rspec'
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Alex Svirin
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.
@@ -0,0 +1,53 @@
1
+ Overview
2
+ ========
3
+
4
+ composite_range is a simple class if you need to deal with more than one range at once.
5
+
6
+ Installation
7
+ ============
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'composite_range'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install composite_range
20
+
21
+ Usage
22
+ =====
23
+
24
+ You can simply create a composite range by passing all ranges that you plan to deal with:
25
+
26
+ range = CompositeRange.new([1..10, 12..45, 37..98])
27
+
28
+ If you have some ranges to exclude from the main one:
29
+
30
+ range = CompositeRange.new(1..20, exclude: [3..5, 9..14])
31
+
32
+ After creating of instance, you can do the range operations: include?, cover?, begin, end, first, last.
33
+
34
+ Adding ranges dynamically:
35
+
36
+ range << (2..12)
37
+
38
+ or
39
+
40
+ range[] = (2..12)
41
+
42
+ Getting range by index:
43
+
44
+ range[index]
45
+
46
+ Contributing
47
+ ============
48
+
49
+ 1. Fork it
50
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
51
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
52
+ 4. Push to the branch (`git push origin my-new-feature`)
53
+ 5. Create new Pull Request
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,15 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Hck"]
5
+ gem.homepage = "http://github.com/hck/composite_range"
6
+ gem.description = %q{CompositeRange class allows you to work with few ranges in one container}
7
+ gem.summary = %q{Simple composite ranges}
8
+
9
+ gem.files = `git ls-files`.split($\)
10
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
11
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
12
+ gem.name = "composite_range"
13
+ gem.require_paths = ["lib"]
14
+ gem.version = '0.1'
15
+ end
@@ -0,0 +1,94 @@
1
+ class CompositeRange
2
+ include Enumerable
3
+
4
+ attr_accessor :ranges
5
+
6
+ def initialize(args = [], options={})
7
+ unless options[:exclude]
8
+ @ranges = args.is_a?(Array) ? args : [args]
9
+ return
10
+ end
11
+
12
+ options[:exclude] = [options[:exclude]] unless options[:exclude].is_a? Array
13
+
14
+ @ranges = options[:exclude].inject([args]) do |res, r|
15
+ res.map do |range|
16
+ if range.include?(r.begin) and range.include?(r.end)
17
+ val = []
18
+ val << (range.begin..r.begin) if range.begin != r.begin
19
+ val << (r.end..range.end) if range.end != r.end
20
+ val
21
+ elsif range.include?(r.begin)
22
+ (range.begin..r.begin) if range.begin != r.begin
23
+ elsif range.include?(r.end)
24
+ (r.end..range.end) if range.end != r.end
25
+ else
26
+ range
27
+ end
28
+ end.flatten.compact
29
+ end
30
+ end
31
+
32
+ def ==(obj)
33
+ obj.respond_to?(:ranges) ? self.ranges == obj.ranges : self.ranges == obj
34
+ end
35
+
36
+ def eql?(obj)
37
+ obj.is_a?(self.class) and self == obj
38
+ end
39
+
40
+ def [](key)
41
+ @ranges[key]
42
+ end
43
+
44
+ def []=(val)
45
+ if val.is_a?(::Range) or val.is_a?(self.class)
46
+ @ranges << val
47
+ else
48
+ raise 'Only arguments with class Range or CompositeRange allowed'
49
+ end
50
+ end
51
+
52
+ def <<(val)
53
+ if val.is_a?(::Range) or val.is_a?(self.class)
54
+ @ranges.push(val)
55
+ else
56
+ raise 'Only arguments with class Range or CompositeRange allowed'
57
+ end
58
+ end
59
+
60
+ def each(&block)
61
+ @ranges.each(&block)
62
+ end
63
+
64
+ def cover?(val)
65
+ select_ranges{|r| r.cover?(val)}.size > 0
66
+ end
67
+
68
+ def include?(val)
69
+ select_ranges{|r| r.include?(val)}.size > 0
70
+ end
71
+ alias :member? :include?
72
+ alias :=== :include?
73
+
74
+ def begin
75
+ ranges.min_by{|r| r.send(__method__)}.send(__method__)
76
+ end
77
+
78
+ def end
79
+ ranges.max_by{|r| r.send(__method__)}.send(__method__)
80
+ end
81
+
82
+ def first(n=1)
83
+ n == 1 ? ranges.first : ranges.first(n)
84
+ end
85
+
86
+ def last(n=1)
87
+ n == 1 ? ranges.last : ranges.last(n)
88
+ end
89
+
90
+ private
91
+ def select_ranges(&block)
92
+ @ranges.select(&block)
93
+ end
94
+ end
@@ -0,0 +1,210 @@
1
+ require 'spec_helper'
2
+
3
+ describe CompositeRange do
4
+ it 'creates new object and fills ranges if passed' do
5
+ subject.class.new.ranges.should eq([])
6
+ subject.class.new((1..5)).ranges.should eq([(1..5)])
7
+ end
8
+
9
+ it 'creates new object and splits passed range to smaller ones by ranges, passed in :exclude option' do
10
+ subject.class.new(1..20, exclude: 3..5).ranges.should eq([1..3, 5..20])
11
+ subject.class.new(1..20, exclude: 3..20).ranges.should eq([1..3])
12
+ subject.class.new(1..20, exclude: [3..5, 12..13, 16..19]).ranges.should eq([1..3, 5..12, 13..16, 19..20])
13
+ subject.class.new(1..20, exclude: [3..5, 12..13, 16..20]).ranges.should eq([1..3, 5..12, 13..16])
14
+ end
15
+
16
+ describe '#[]' do
17
+ before(:each) do
18
+ @range = subject.class.new [(1..10), (13..21)]
19
+ end
20
+
21
+ it 'gets element by index' do
22
+ @range[0].should eq(1..10)
23
+ @range[1].should eq(13..21)
24
+ end
25
+
26
+ it 'gets nil if index not exists' do
27
+ @range[2].should be_nil
28
+ end
29
+
30
+ it 'raises exception if not integer index provided' do
31
+ ->{@range['key']}.should raise_error
32
+ end
33
+ end
34
+
35
+ describe '#[]=' do
36
+ before(:each) do
37
+ @range = subject.class.new
38
+ end
39
+
40
+ it 'assigns element is it is CompositeRange or Range object' do
41
+ @range[] = (1..10)
42
+ @range[] = subject.class.new
43
+
44
+ @range[0].should eq(1..10)
45
+ @range[1].should eq(subject.class.new)
46
+ end
47
+
48
+ it 'raises error if element is not Range or CompositeRange' do
49
+ ->{@range[] = 1}.should raise_error
50
+ end
51
+ end
52
+
53
+ describe '#<<' do
54
+ before(:each) do
55
+ @range = subject.class.new
56
+ end
57
+
58
+ it 'pushes element to composite range if it is CompositeRange or Range object' do
59
+ @range << (1..10)
60
+ @range << subject.class.new
61
+ @range[0].should eq(1..10)
62
+ @range[1].should eq(subject.class.new)
63
+ end
64
+
65
+ it 'raises error if element is not Range or CompositeRange' do
66
+ ->{@range << 1}.should raise_error
67
+ end
68
+ end
69
+
70
+ describe '#each' do
71
+ before(:each) do
72
+ @range = subject.class.new([1..5, 8..10, 23..45])
73
+ res = []
74
+ @range.each{|r| res << r.begin + 5}
75
+ res.should == [6, 13, 28]
76
+ end
77
+
78
+ it 'yields block for each element in @ranges' do
79
+
80
+ end
81
+ end
82
+
83
+ describe '#cover?' do
84
+ before(:each) do
85
+ @range = subject.class.new([1..10, 12..45, -18..-3])
86
+ end
87
+
88
+ it 'returns true if one of @ranges covers passed object' do
89
+ @range.cover?(10).should be_true
90
+ @range.cover?(12).should be_true
91
+ @range.cover?(15).should be_true
92
+ @range.cover?(-17).should be_true
93
+ end
94
+
95
+ it 'returns false if passed object is not covered in any of @ranges' do
96
+ @range.cover?(0).should be_false
97
+ @range.cover?(11).should be_false
98
+ @range.cover?(46).should be_false
99
+ @range.cover?(-2).should be_false
100
+ end
101
+ end
102
+
103
+ describe '#include?' do
104
+ before(:each) do
105
+ @range = subject.class.new([1..10, 12..45, -18..-3])
106
+ end
107
+
108
+ it 'returns true if one of @ranges includes passed object' do
109
+ @range.include?(10).should be_true
110
+ @range.include?(12).should be_true
111
+ @range.include?(15).should be_true
112
+ @range.include?(-17).should be_true
113
+ end
114
+
115
+ it 'returns false if passed object is not included in any of @ranges' do
116
+ @range.include?(0).should be_false
117
+ @range.include?(11).should be_false
118
+ @range.include?(46).should be_false
119
+ @range.include?(-2).should be_false
120
+ end
121
+ end
122
+
123
+ describe '#begin' do
124
+ before(:each) do
125
+ @range = subject.class.new
126
+ end
127
+
128
+ it 'returns min object from all ranges start points' do
129
+ @range << (1..10)
130
+ @range << (3..12)
131
+ @range << (21..85)
132
+ @range.begin.should eq(1)
133
+ end
134
+ end
135
+
136
+ describe '#end' do
137
+ before(:each) do
138
+ @range = subject.class.new
139
+ end
140
+
141
+ it 'returns min object from all ranges end points' do
142
+ @range << (1..10)
143
+ @range << (3..12)
144
+ @range << (21..85)
145
+ @range.end.should eq(85)
146
+ end
147
+ end
148
+
149
+ describe '#first' do
150
+ before(:each) do
151
+ @range = subject.class.new
152
+ @range << (1..10)
153
+ @range << (3..12)
154
+ @range << (21..85)
155
+ end
156
+
157
+ it 'returns first element if n is not specified' do
158
+ @range.first.should eq(1..10)
159
+ end
160
+
161
+ it 'returns first n element if n is specified' do
162
+ @range.first(2).should eq([1..10, 3..12])
163
+ end
164
+ end
165
+
166
+ describe '#last' do
167
+ before(:each) do
168
+ @range = subject.class.new
169
+ @range << (1..10)
170
+ @range << (3..12)
171
+ @range << (21..85)
172
+ end
173
+
174
+ it 'returns last element if n is not specified' do
175
+ @range.last.should eq(21..85)
176
+ end
177
+
178
+ it 'returns last n element if n is specified' do
179
+ @range.last(2).should eq([3..12, 21..85])
180
+ end
181
+ end
182
+
183
+ describe '#==' do
184
+ it 'composite ranges are == if @ranges are empty' do
185
+ @range1 = subject.class.new
186
+ @range2 = subject.class.new
187
+ @range1.should == @range2
188
+ end
189
+
190
+ it 'composite ranges are == if @ranges are equal' do
191
+ @range1 = subject.class.new([1..5, 7..10])
192
+ @range2 = subject.class.new([1..5, 7..10])
193
+ @range1.should == @range2
194
+ end
195
+ end
196
+
197
+ describe '#eql?' do
198
+ it 'composite ranges are eql? if @ranges are equal' do
199
+ @range1 = subject.class.new([1..5, 7..10])
200
+ @range2 = subject.class.new([1..5, 7..10])
201
+ @range1.should == @range2
202
+ end
203
+
204
+ it 'ranges are not eql? if they are not of the same class' do
205
+ @range1 = subject.class.new(1..5)
206
+ @range2 = (1..5)
207
+ @range1.should_not == @range2
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
4
+ require 'rspec'
5
+ require 'composite_range'
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: composite_range
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Hck
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-28 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: CompositeRange class allows you to work with few ranges in one container
15
+ email:
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - .gitignore
21
+ - .rspec
22
+ - Gemfile
23
+ - LICENSE
24
+ - README.md
25
+ - Rakefile
26
+ - composite_range.gemspec
27
+ - lib/composite_range.rb
28
+ - spec/composite_range_spec.rb
29
+ - spec/spec_helper.rb
30
+ homepage: http://github.com/hck/composite_range
31
+ licenses: []
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 1.8.24
51
+ signing_key:
52
+ specification_version: 3
53
+ summary: Simple composite ranges
54
+ test_files:
55
+ - spec/composite_range_spec.rb
56
+ - spec/spec_helper.rb