rangeset 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -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,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rangeset.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Darrick Wiebe
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,16 @@
1
+ RangeSet
2
+ ========
3
+
4
+ Do simple set operations on Ruby Ranges and RangeSets
5
+
6
+ RangeSet objects act like Ranges except that they may have any number of
7
+ gaps.
8
+
9
+ Limitations
10
+ -----------
11
+
12
+ This library is tested against integer ranges for my use case, but any
13
+ Comparable type should work, possible with a little bit of effort.
14
+
15
+ I have not implemented more of the Range interface in RangeSet than I
16
+ needed. If you need additional functionality, pull requests are welcome.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,86 @@
1
+
2
+ # left [1..10, 20..30, 40..50]
3
+ # right [ 25..45, 49..55]
4
+ #
5
+ # left ..........---------..........---------..........
6
+ # right ------------------------....................---......
7
+ # in left only .......... ..... ...
8
+ # in both ..... ......
9
+ # in right only ......... .....
10
+ # [[1..10, 20..24, 46..50],
11
+ # [25..30, 40..45],
12
+ # [31..39, 51..55]]
13
+
14
+ class Range
15
+ class << self
16
+ # [only in left, in both, only in right]
17
+ def diff(left, right)
18
+ if left.is_a? RangeSet or right.is_a? RangeSet
19
+ return RangeSet.diff left, right
20
+ end
21
+ a = left.first - right.first
22
+ if left.first == right.first
23
+ if left.last == right.last
24
+ # 1..3, 1..3 -> [nil, 1..3, nil]
25
+ [nil, left, nil]
26
+ elsif left.last > right.last
27
+ # 1..4, 1..2 -> [3..4, 1..2, nil]
28
+ [right.last.next..left.last, right, nil]
29
+ else
30
+ # 1..2, 1..4 -> [nil, 1..2, 3..4]
31
+ [nil, left, left.last.next..right.last]
32
+ end
33
+ elsif left.first < right.first
34
+ if left.last == right.last
35
+ # 1..4, 3..4 -> [1..2, 3..4, nil]
36
+ [left.first..(right.first - 1), right.first..right.last, nil]
37
+ elsif left.last > right.last
38
+ # 1..6, 3..4 -> [[1..2, 5..6], 3..4, nil]
39
+ [RangeSet.new(left.first..(right.first - 1), (right.last.next..left.last)), right, nil]
40
+ elsif left.last < right.last
41
+ if left.last < right.first
42
+ # 1..3, 5..6 -> [1..3, nil, 5..6]
43
+ [left, nil, right]
44
+ elsif left.last >= right.first
45
+ # 1..4, 3..6 -> [1..2, 3..4, 5..6]
46
+ [left.first..(right.first - 1), right.first..left.last, left.last.next..right.last]
47
+ end
48
+ end
49
+ else
50
+ diff(right, left).reverse
51
+ end
52
+ end
53
+ end
54
+
55
+ def diff(other)
56
+ Range.diff self, other
57
+ end
58
+
59
+ def difference(other)
60
+ diff(other)[0]
61
+ end
62
+
63
+ def intersection(other)
64
+ diff(other)[1]
65
+ end
66
+
67
+ def union(other)
68
+ RangeSet.build [self, other]
69
+ end
70
+
71
+ def ranges
72
+ [self]
73
+ end
74
+
75
+ unless instance_methods.include? :equal_without_rangeset
76
+ alias :equal_without_rangeset :==
77
+ end
78
+
79
+ def ==(other)
80
+ if other.is_a? RangeSet
81
+ other == self
82
+ else
83
+ equal_without_rangeset other
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,3 @@
1
+ class RangeSet
2
+ VERSION = "0.1.0"
3
+ end
data/lib/rangeset.rb ADDED
@@ -0,0 +1,179 @@
1
+ p version: require("rangeset/version")
2
+ p range: require("rangeset/range")
3
+
4
+ class RangeSet
5
+ class << self
6
+ def build(ranges)
7
+ if ranges
8
+ if ranges.is_a? Array
9
+ ranges = ranges.compact
10
+ if ranges.length == 1 and ranges.first.is_a? Range
11
+ ranges.first
12
+ elsif ranges.count == 0
13
+ nil
14
+ else
15
+ RangeSet.new *ranges
16
+ end
17
+ elsif ranges.is_a? Range or ranges.is_a? RangeSet
18
+ ranges
19
+ elsif ranges.is_a? Fixnum
20
+ ranges..ranges
21
+ end
22
+ end
23
+ end
24
+
25
+ def union(left, right)
26
+ if left.is_a? Array
27
+ raise 'wtf'
28
+ end
29
+ if not left
30
+ right
31
+ elsif not right
32
+ left
33
+ elsif left.is_a? Range and right.is_a? Range
34
+ left.union right
35
+ else
36
+ RangeSet.new right, left
37
+ end
38
+ end
39
+
40
+ def intersection(left, right)
41
+ if not left or not right
42
+ nil
43
+ elsif left.is_a? Range and right.is_a? Range
44
+ left.intersection right
45
+ else
46
+ left.ranges.reduce(nil) do |result, left|
47
+ intersections = right.ranges.map do |right|
48
+ left.intersection right
49
+ end
50
+ union(result, RangeSet.build(intersections))
51
+ end
52
+ end
53
+ end
54
+
55
+ def difference(left, right)
56
+ if not right
57
+ left
58
+ elsif left
59
+ left.ranges.reduce(nil) do |result, l|
60
+ in_left = right.ranges.map do |r|
61
+ l.difference(r)
62
+ end
63
+ in_left = in_left.reduce { |int, il|
64
+ intersection int, il
65
+ }
66
+ union result, in_left
67
+ end
68
+ end
69
+ end
70
+
71
+ def diff(left, right, intersect_only = false)
72
+ return [left, nil, nil] unless right
73
+ return [nil, nil, right] unless left
74
+ int = intersection(left, right)
75
+ in_left = left.difference int
76
+ in_right = right.difference int
77
+ [in_left, int, in_right]
78
+ end
79
+ end
80
+
81
+ include Enumerable
82
+
83
+ attr_reader :ranges
84
+
85
+ def initialize(*ranges)
86
+ if ranges.length > 1
87
+ @ranges = combine ranges
88
+ else
89
+ if ranges.first.is_a? RangeSet
90
+ @ranges = ranges.first.ranges
91
+ else
92
+ @ranges = ranges
93
+ end
94
+ end
95
+ end
96
+
97
+ def combine(ranges)
98
+ ranges = ranges.compact.flat_map do |r|
99
+ if r.is_a? RangeSet
100
+ r.ranges
101
+ elsif r.is_a? Range
102
+ [r]
103
+ elsif r.is_a? Fixnum
104
+ [r..r]
105
+ else
106
+ []
107
+ end
108
+ end
109
+ ranges = ranges.compact.sort_by(&:first)
110
+ result = [ranges.first]
111
+ ranges[1..-1].each do |r|
112
+ l = result.last
113
+ if l.last >= r.first
114
+ if l.last >= r.last
115
+ next
116
+ else
117
+ result.pop
118
+ r = l.first..r.last
119
+ end
120
+ elsif l.last + 1 == r.first
121
+ result.pop
122
+ r = l.first..r.last
123
+ end
124
+ result.push r
125
+ end
126
+ result
127
+ end
128
+
129
+ def gaps
130
+ RangeSet.build ranges.each_cons(2).map { |a, b| (a.last + 1)..(b.first - 1) }
131
+ end
132
+
133
+ def each(&block)
134
+ if block
135
+ ranges.each do |range|
136
+ range.each &block
137
+ end
138
+ else
139
+ to_enum :each
140
+ end
141
+ end
142
+
143
+ def first
144
+ ranges.first.first
145
+ end
146
+
147
+ def last
148
+ ranges.last.last
149
+ end
150
+
151
+ def include?(n)
152
+ ranges.any? { |r| r.include? n }
153
+ end
154
+
155
+ def diff(other)
156
+ RangeSet.diff self, other
157
+ end
158
+
159
+ def union(other)
160
+ RangeSet.union self, other
161
+ end
162
+
163
+ def intersection(other)
164
+ RangeSet.intersection self, other
165
+ end
166
+
167
+ def difference(other)
168
+ RangeSet.difference self, other
169
+ end
170
+
171
+ def ==(other)
172
+ !!(
173
+ if other.is_a? RangeSet
174
+ ranges == other.ranges
175
+ elsif other.is_a? Range and ranges.count == 1
176
+ ranges == [other]
177
+ end)
178
+ end
179
+ end
data/rangeset.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rangeset/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "rangeset"
8
+ gem.version = RangeSet::VERSION
9
+ gem.authors = ["Darrick Wiebe"]
10
+ gem.email = ["dw@xnlogic.com"]
11
+ gem.description = %q{Set operations on ranges and range sets}
12
+ gem.summary = %q{Set operations on ranges and range sets}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe RangeSet do
4
+ subject { RangeSet.new 10..20, 30..40 }
5
+
6
+ it 'should equal itself' do
7
+ (subject == subject).should be_true
8
+ end
9
+
10
+ it 'should equal same' do
11
+ (subject == RangeSet.new(10..20, 30..40)).should be_true
12
+ end
13
+
14
+ it 'should not equal different' do
15
+ (subject == RangeSet.new(10..21, 30..40)).should be_false
16
+ end
17
+
18
+ it 'should equal equivalent range' do
19
+ (RangeSet.new(10..20) == (10..20)).should be_true
20
+ end
21
+
22
+ context 'same' do
23
+ let(:other) { RangeSet.new 10..20, 30..40 }
24
+ it "should be all same" do
25
+ subject.diff(other).should == [nil, subject, nil]
26
+ subject.diff(other).should == [nil, other, nil]
27
+ end
28
+ end
29
+
30
+ context 'subset' do
31
+ let(:other) { RangeSet.new 30..40 }
32
+ it do
33
+ subject.diff(other).should == [10..20, 30..40, nil]
34
+ end
35
+ end
36
+
37
+ context 'no overlap' do
38
+ let(:other) { RangeSet.new 130..140 }
39
+ it do
40
+ subject.diff(other).should == [subject, nil, 130..140]
41
+ end
42
+ end
43
+
44
+ context 'partial overlap' do
45
+ let(:other) { RangeSet.new 35..140 }
46
+ it do
47
+ subject.diff(other).should == [RangeSet.new(10..20, 30..34), 35..40, 41..140]
48
+ end
49
+ end
50
+
51
+ context 'engulfs' do
52
+ let(:other) { RangeSet.new 11..19 }
53
+ it do
54
+ subject.diff(other).should == [RangeSet.new(10..10, 20..20, 30..40), other, nil]
55
+ end
56
+ end
57
+
58
+ context 'engulfs2' do
59
+ let(:other) { RangeSet.new 11..19, 35..36 }
60
+ it do
61
+ subject.diff(other).should == [RangeSet.new(10..10, 20..20, 30..34, 37..40), other, nil]
62
+ end
63
+ end
64
+
65
+ context 'engulfed by' do
66
+ let(:other) { RangeSet.new 1..50 }
67
+ it do
68
+ subject.diff(other).should == [nil, subject, RangeSet.new(1..9, 21..29, 41..50)]
69
+ end
70
+ end
71
+ end
72
+
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Range do
4
+ subject { 10..20 }
5
+
6
+ it 'should equal itself' do
7
+ (subject == subject).should be_true
8
+ end
9
+
10
+ it 'should equal same' do
11
+ (subject == (10..20)).should be_true
12
+ end
13
+
14
+ it 'should not equal different' do
15
+ (subject == (10..21)).should be_false
16
+ end
17
+
18
+ it 'should equal equivalent RangeSet' do
19
+ ((10..20) == RangeSet.new(10..20)).should be_true
20
+ end
21
+
22
+ describe '#diff' do
23
+ subject { 30..40 }
24
+
25
+ context 'partial overlap' do
26
+ let(:other) { 35..140 }
27
+ it do
28
+ subject.diff(other).should == [30..34, 35..40, 41..140]
29
+ end
30
+ end
31
+
32
+ context 'with a RangeSet' do
33
+ let(:other) { RangeSet.new(1..10, 30..35) }
34
+ it do
35
+ subject.diff(other).should == [36..40, 30..35, 1..10]
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,19 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ require 'lib/rangeset'
13
+
14
+ # Run specs in random order to surface order dependencies. If you find an
15
+ # order dependency and want to debug it, you can fix the order by providing
16
+ # the seed, which is printed after each run.
17
+ # --seed 1234
18
+ config.order = 'random'
19
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rangeset
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Darrick Wiebe
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-19 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Set operations on ranges and range sets
15
+ email:
16
+ - dw@xnlogic.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - .rspec
23
+ - Gemfile
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - lib/rangeset.rb
28
+ - lib/rangeset/range.rb
29
+ - lib/rangeset/version.rb
30
+ - rangeset.gemspec
31
+ - spec/rangeset/range_set_spec.rb
32
+ - spec/rangeset/range_spec.rb
33
+ - spec/spec_helper.rb
34
+ homepage: ''
35
+ licenses: []
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: !binary |-
45
+ MA==
46
+ none: false
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: !binary |-
52
+ MA==
53
+ none: false
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 1.8.24
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: Set operations on ranges and range sets
60
+ test_files:
61
+ - spec/rangeset/range_set_spec.rb
62
+ - spec/rangeset/range_spec.rb
63
+ - spec/spec_helper.rb
64
+ has_rdoc: