overlaps 0.0.1

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: 47f94fb75dd165cd51d32e6e369a80523ae94772
4
+ data.tar.gz: c7ad701a70c19715d40e81ca981b63014da3eeec
5
+ SHA512:
6
+ metadata.gz: efce43bad7f10d55ac8c6b76a1e43a31efbd99be2b9fb199320aa57a925520ddbf967ce0ab2e0bf780871ef86968aa9dc2f63677a4fd1aaff91cfb059c2858bd
7
+ data.tar.gz: 93d01683a5c86dc4470836eb4a17c10512d8008df6b9d79be6ae5c5504cd9ec9e88a30397cf1892ddf167fcde5a2273b00a70fd7a11516a6dd97085777605637
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 overlaps.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ryan Stenberg
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,94 @@
1
+ # Overlaps
2
+
3
+ The Overlaps module enables a class to either count or find overlaps in an array of:
4
+ 1. Range objects
5
+ 2. Objects that have start and end point-type attributes that form a valid range.
6
+
7
+ The main purpose of Overlaps -- given a set of ranges -- is to find all possible overlaps and have information about which ranges belong to/share in a given overlap.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'overlaps'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install overlaps
22
+
23
+ ## Usage
24
+
25
+ Include the Overlaps module in the class you want to manage overlaps in your application (class Klass; include Overlaps; end). The class now has access to the count_overlaps and find_overlaps methods (from Overlaps::ClassMethods).
26
+
27
+ Both methods expect an array of Range objects where the start and end points for each range in the array must all be of the same class.
28
+
29
+ array_of_ranges = [1..100, 25..55, 30..110, 10..27]
30
+ Klass.count_overlaps(array_of_ranges)
31
+ Klass.find_overlaps(array_of_ranges)
32
+
33
+ You may also feed them an array of objects with a hash containing the start point attribute and end point attribute (as symbol or string):
34
+
35
+ class ObjWithRange
36
+ attr_accessor :s, :e
37
+ def initialize(attrs = {})
38
+ @s, @e = attrs[:s], attrs[:e]
39
+ end
40
+ end
41
+
42
+ array_of_objects = [ObjWithRange.new(s: 1, e: 10), ObjWithRange.new(s: 1, e: 5), ObjWithRange.new(s: 3, e: 6)]
43
+ Klass.count_overlaps(array_of_objects, start_attr: :s, end_attr: :e)
44
+ Klass.find_overlaps(array_of_objects, start_attr: 's', end_attr: 'e')
45
+
46
+ Overlaps::ClassMethods::count_overlaps returns a Fixnum count:
47
+
48
+ Klass.count_overlaps(array_of_ranges) => 5
49
+
50
+ Overlaps::ClassMethods::find_overlaps returns an array of Overlap objects, where each overlap captures the following (with example output):
51
+
52
+ Start Point Value
53
+
54
+ overlap.start_point => 1
55
+
56
+ End Point Value
57
+
58
+ overlap.end_point => 5
59
+
60
+ Range
61
+
62
+ overlap.range => 1..5
63
+
64
+ IDs of Each Range that Shares the Overlap
65
+
66
+ overlap.ids => [0,1]
67
+
68
+ Also, you may pass a specific attribute to use as the id. When an array of Ranges is given, the id is set to the index of range as it appears in the original input array. When an array of non-Ranges is given, it looks for an :id attribute by default. If an :id attribute is not found, it falls back to the index of the object as it appears in the original input array. The following section provides an example of setting the id to a specific object attribute.
69
+
70
+ ## Example Use Case
71
+ Say we had a list of Zombie objects (zombies, where the Zombie class has included Overlaps) with :name, :date_of_birth, and :date_turned_zombie attributes that looked something like this:
72
+
73
+ zombies => [#<Zombie:0x2c78220 @name="Bob", @date_of_birth=#<Date: 1920-01-21 ((2422345j,0s,0n),+0s,2299161j)>, @date_turned_zombie=#<Date: 1950-06-20 ((2433453j,0s,0n),+0s,2299161j)>>,
74
+ #<Zombie:0x2c70e88 @name="Karen", @date_of_birth=#<Date: 1927-03-10 ((2424950j,0s,0n),+0s,2299161j)>, @date_turned_zombie=#<Date: 1950-07-04 ((2433467j,0s,0n),+0s,2299161j)>>,
75
+ #<Zombie:0x2c69a60 @name="Frank", @date_of_birth=#<Date: 1938-10-15 ((2429187j,0s,0n),+0s,2299161j)>, @date_turned_zombie=#<Date: 1950-11-27 ((2433613j,0s,0n),+0s,2299161j)>>]
76
+
77
+ If we wanted to find the overlapping periods where each of them were alive and not yet a zombie, we could call find_overlaps like so:
78
+
79
+ Zombie.find_overlaps(zombies, start_attr: :date_of_birth, end_attr: :date_turned_zombie, id_attr: :name)
80
+ => [#<Overlaps::Overlap:0x2f47d10 @start_point=#<Date: 1938-10-15 ((2429187j,0s,0n),+0s,2299161j)>, @end_point=#<Date: 1950-06-20((2433453j,0s,0n),+0s,2299161j)>, @ids=["Bob", "Karen", "Frank"]>,
81
+ #<Overlaps::Overlap:0x2f47ce0 @start_point=#<Date: 1927-03-10 ((2424950j,0s,0n),+0s,2299161j)>, @end_point=#<Date: 1950-06-20 ((2433453j,0s,0n),+0s,2299161j)>, @ids=["Bob", "Karen"]>,
82
+ #<Overlaps::Overlap:0x2f47c68 @start_point=#<Date: 1938-10-15 ((2429187j,0s,0n),+0s,2299161j)>, @end_point=#<Date: 1950-07-04 ((2433467j,0s,0n),+0s,2299161j)>, @ids=["Karen", "Frank"]>]
83
+
84
+ If we simply wanted to count the number of overlapping periods, we could call count_overlaps like so:
85
+
86
+ Klass.count_overlaps(zombies, start_attr: :date_of_birth, end_attr: :date_turned_zombie) => 3
87
+
88
+ ## Contributing
89
+
90
+ 1. Fork it
91
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
92
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
93
+ 4. Push to the branch (`git push origin my-new-feature`)
94
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,9 @@
1
+ require_relative 'point'
2
+
3
+ module Overlaps
4
+ class EndPoint < Point
5
+ def end?
6
+ true
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ module Overlaps
2
+ module InputParser
3
+ class MissingParameter < StandardError; end
4
+
5
+ def valid_input?(objects, start_attr = nil, end_attr = nil)
6
+ raise TypeError, "Expecting an Array object, got a #{objects.class} instead." unless objects.class == Array #Ensure Overlaps is receiving an Array object.
7
+ classes_in_range = []
8
+ objects.each do |object|
9
+ if object.class == Range
10
+ classes_in_range << object.first.class #Add class of start element to classes_in_range tracker
11
+ classes_in_range << object.last.class #Add class of end element to classes_in_range tracker
12
+ else
13
+ raise MissingParameter unless start_attr && end_attr #The names of the start_attr and end_attr must be passed with an array of ActiveRecord objects
14
+ classes_in_range << object.send(start_attr.to_sym).class #Add class of start element to classes_in_range tracker
15
+ classes_in_range << object.send(end_attr.to_sym).class #Add class of end element to classes_in_range tracker
16
+ end
17
+ raise TypeError, 'All ranges must share the same class-type for start and end points.' if classes_in_range.uniq.size > 1 #Ensure all range start/ends are of the same class.
18
+ end
19
+ true #If we get through each array element, verifying it is both a range and that its start/end points share a class with all the other ranges, return true.
20
+ end
21
+
22
+ def grab_id(obj, id = nil)
23
+ id ||= :id
24
+ obj.respond_to?(id) ? obj.send(id) : nil
25
+ end
26
+
27
+ def convert_range_to_points(range, id)
28
+ return StartPoint.new(range.first, id), EndPoint.new(range.last, id)
29
+ end
30
+
31
+ def parse_input(objects, accessors = {})
32
+ valid_input?(objects, accessors[:start_attr], accessors[:end_attr])
33
+ output = []
34
+ objects.each_with_index do |object, index|
35
+ id = grab_id(object, accessors[:id_attr]) || index
36
+ if object.class == Range
37
+ output << convert_range_to_points(object, id)
38
+ else
39
+ range = object.send(accessors[:start_attr])..object.send(accessors[:end_attr])
40
+ output << convert_range_to_points(range, id)
41
+ end
42
+ end
43
+ output.flatten!.sort_by {|point| point.value}
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,15 @@
1
+ module Overlaps
2
+ class Overlap
3
+ attr_accessor :start_point, :end_point, :ids
4
+
5
+ def initialize(start_point, end_point, ids)
6
+ @start_point = start_point
7
+ @end_point = end_point
8
+ @ids = ids
9
+ end
10
+
11
+ def range
12
+ (start_point..end_point)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module Overlaps
2
+ class Point
3
+ attr_accessor :value, :id
4
+
5
+ def initialize(value, id)
6
+ @value = value
7
+ @id = id
8
+ end
9
+
10
+ def start?
11
+ false
12
+ end
13
+
14
+ def end?
15
+ false
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ require_relative 'point'
2
+
3
+ module Overlaps
4
+ class StartPoint < Point
5
+ def start?
6
+ true
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Overlaps
2
+ VERSION = "0.0.1"
3
+ end
data/lib/overlaps.rb ADDED
@@ -0,0 +1,67 @@
1
+ require_relative 'overlaps/version'
2
+ require_relative 'overlaps/input_parser'
3
+ require_relative 'overlaps/start_point'
4
+ require_relative 'overlaps/end_point'
5
+ require_relative 'overlaps/overlap'
6
+
7
+ require 'date'
8
+ require 'time'
9
+
10
+ module Overlaps
11
+ extend Overlaps::InputParser
12
+
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+
17
+ module ClassMethods
18
+ #include Overlaps::InputParser
19
+ def count_overlaps(input_array)
20
+ points = Overlaps::parse_input(input_array) if Overlaps::valid_input?(input_array) #Validate array of ranges, then parse into a sorted array of StartPoint and EndPoint objects.
21
+ overlap_counter = -1 #Starts at negative 1, knowing that the first start element would start the counter at 0.
22
+ overlap_tally = points.inject(0) do |tally, point|
23
+ if point.class == StartPoint
24
+ overlap_counter += 1 #If we run into a start_point, increment the overlap_counter.
25
+ elsif point.class == EndPoint #If we run into an end point..
26
+ tally += overlap_counter #Add the current overlap_counter number of overlaps to the overall tally, noting the number of overlaps that are shared with the end point encountered.
27
+ overlap_counter -= 1 #Decrement the overlap counter, noting that one of the ranges was closed out.
28
+ end
29
+ tally
30
+ end
31
+ end
32
+
33
+ def find_overlaps(input_array, accessors = {})
34
+ points = Overlaps::parse_input(input_array, accessors)
35
+
36
+ overlaps = []
37
+ overlap_counter = -1 #Starts at negative 1, knowing that the first start element would start the counter at 0.
38
+
39
+ points.inject([]) do |start_stack, point|
40
+ if point.class == StartPoint
41
+ overlap_counter += 1 #If we run into a start_point, increment the overlap_counter.
42
+ start_stack.push(point)
43
+ elsif point.class == EndPoint
44
+ overlaps_to_make = overlap_counter #If we run into an end_point, capture the current overlap counter pre-decrement so we know how many overlaps to construct.
45
+ overlap_counter -= 1 #If we run into an end_point, decrement the overlap_counter.
46
+ overlaps << build_overlaps(start_stack.dup, point, overlaps_to_make) #Returns array of overlaps (as hashes) in format {overlap: (start..end), indices: [all start indices]}
47
+ start_stack.delete_if {|start_point| start_point.id == point.id} #Delete the start element from the start_stack if it was the starting point for the end_point found.
48
+ end
49
+ start_stack #Pass the current start_stack back into the inject to keep a running list of starts.
50
+ end
51
+ overlaps.flatten! #To end up with a single array of overlap hashes in format {overlap: (start..end), indeces: [all indices of original ranges that cover the given overlap]}
52
+ end
53
+
54
+ private
55
+ def build_overlaps(start_stack, end_point, overlaps_to_make)
56
+ #Take a stack of start points, an end point, and a number of overlaps to make.
57
+ overlaps = []
58
+ start_ids = start_stack.map {|start_point| start_point.id} #Map ids for all start points in the stack.
59
+ overlaps_to_make.times do #Based on the overlap_counter value, we want to build that many overlaps starting with the closest start point to the given end point.
60
+ #Create overlap with the top of the start stack and given end point, attributing all start ids as sharing the overlap, then pop the start point off of the stack.
61
+ overlaps << Overlaps::Overlap.new(start_stack.pop.value, end_point.value, start_ids.dup)
62
+ start_ids.pop #Remove the id from the discarded start point from the list of start point ids.
63
+ end
64
+ overlaps
65
+ end
66
+ end
67
+ end
data/overlaps.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'overlaps/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "overlaps"
8
+ spec.version = Overlaps::VERSION
9
+ spec.authors = ["Ryan Stenberg"]
10
+ spec.email = ["h0tl33t@gmail.com"]
11
+ spec.description = %q{Overlaps enables a class to either count or find overlaps between a set of ranges.}
12
+ spec.summary = %q{Overlaps, when included in a class, enables that class to #count_overlaps or #find_overlaps when given an array of Range objects or objects that have start and end point-type attributes.}
13
+ spec.homepage = "https://github.com/h0tl33t/overlaps"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ module Overlaps
4
+ describe EndPoint do
5
+ subject {EndPoint.new(10, 1)}
6
+
7
+ it {should respond_to(:value)}
8
+ it {should respond_to(:id)}
9
+
10
+ its(:value) {should == 10}
11
+ its(:id) {should == 1}
12
+
13
+ it {should_not be_start}
14
+ it {should be_end}
15
+ end
16
+ end
@@ -0,0 +1,168 @@
1
+ require 'spec_helper'
2
+
3
+ module Overlaps
4
+ describe InputParser do
5
+ subject(:klass) {Class.new {extend InputParser}}
6
+ let(:valid_range_input) {[(1..10), (1..5), (3..7)]}
7
+
8
+ let(:valid_obj_input) {[ObjectWithRange.new(1, 10, 1), ObjectWithRange.new(1, 5, 2), ObjectWithRange.new(3, 6, 3)] }
9
+
10
+ context '#valid_input?' do
11
+ context 'raises an error when given' do
12
+ it 'a non-array object' do
13
+ expect {klass.valid_input?(Hash.new)}.to raise_error(TypeError)
14
+ end
15
+
16
+ context 'an array' do
17
+ context 'of non-Range objects' do
18
+ it 'without providing the accessor methods for the start and end points' do
19
+ expect {klass.valid_input?(valid_obj_input)}.to raise_error(InputParser::MissingParameter)
20
+ end
21
+
22
+ it 'where the class of the start and end points of each object are not all the same' do
23
+ invalid_obj_input = valid_obj_input << ObjectWithRange.new(4, 'a', 'z')
24
+ expect {klass.valid_input?(invalid_obj_input, :start_point, :end_point)}.to raise_error(TypeError)
25
+ end
26
+ end
27
+
28
+ context 'of Range objects' do
29
+ it 'where the start and end points of each range are not all the same class' do
30
+ expect {klass.valid_input?([(1..5), ('a'..'b')])}.to raise_error(TypeError)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ context 'returns true when given' do
37
+ it 'an array of ranges where all start/end points are the same class' do
38
+ expect(klass.valid_input?(valid_range_input)).to be_true
39
+ end
40
+
41
+ it 'an array of objects with start and end points that form a valid range and are all of the same class' do
42
+ expect(klass.valid_input?(valid_obj_input, :start_point, :end_point)).to be_true
43
+ end
44
+ end
45
+ end
46
+
47
+ context '#grab_id' do
48
+ it 'returns the value of an id attribute if one exists' do
49
+ obj = ObjectWithRange.new(1, 10, 1)
50
+ expect(klass.grab_id(obj)).to eq(1)
51
+ end
52
+ it 'returns nil if no id attribute is found' do
53
+ expect(klass.grab_id(Object.new)).to be_nil
54
+ end
55
+ end
56
+
57
+ context '#convert_range_to_points' do
58
+ let(:start_point) {klass.convert_range_to_points(1..10, 1).first}
59
+ let(:end_point) {klass.convert_range_to_points(1..10, 1).last}
60
+
61
+ context 'returns a start point' do
62
+ it 'of class StartPoint' do
63
+ expect(start_point).to be_an_instance_of(StartPoint)
64
+ end
65
+
66
+ it 'with a value equal to the first value in the range' do
67
+ expect(start_point.value).to eq(1)
68
+ end
69
+
70
+ it 'with an id' do
71
+ expect(start_point.id).to eq(1)
72
+ end
73
+ end
74
+
75
+ context 'returns an end point' do
76
+ it 'of class EndPoint' do
77
+ expect(end_point).to be_an_instance_of(EndPoint)
78
+ end
79
+
80
+ it 'with a value equal to the last value in the range' do
81
+ expect(end_point.value).to eq(10)
82
+ end
83
+
84
+ it 'with an id' do
85
+ expect(end_point.id).to eq(1)
86
+ end
87
+ end
88
+ end
89
+
90
+ context '#parse_input' do
91
+ context 'returns a collection of start and end points when given valid input' do
92
+ context 'consisting of non-Range objects' do
93
+ let(:output) { klass.parse_input(valid_obj_input, {start_attr: :start_point, end_attr: :end_point})}
94
+
95
+ it 'where the collection is an Array' do
96
+ expect(output).to be_an_instance_of(Array)
97
+ end
98
+
99
+ it 'where the start points are StartPoint objects' do
100
+ expect(output.first).to be_an_instance_of(StartPoint)
101
+ end
102
+
103
+ it 'where the end points are EndPoint objects' do
104
+ expect(output.last).to be_an_instance_of(EndPoint)
105
+ end
106
+
107
+ it 'where the collection is sorted by the value attribute of each point' do
108
+ expect(output).to eq(output.sort_by {|point| point.value})
109
+ end
110
+
111
+ context 'having an id attribute' do
112
+ it 'where the id of each start point point matches that of its originating object' do
113
+ expect(output.first.id).to eq(1)
114
+ end
115
+ it 'where the id of each end point point matches that of its originating object' do
116
+ expect(output.last.id).to eq(1)
117
+ end
118
+ end
119
+
120
+ context 'not having an id attribute' do
121
+ let(:valid_obj_input) {[ObjectWithRange.new(1, 10), ObjectWithRange.new(1, 5), ObjectWithRange.new(3, 6)]}
122
+ it 'where the id of each start point matches the index of its originating object as found in the input array' do
123
+ expect(output.first.id).to eq(0)
124
+ end
125
+
126
+ it 'where the id of each end point matches the index of its originating object as found in the input array' do
127
+ expect(output.last.id).to eq(0)
128
+ end
129
+ end
130
+ end
131
+
132
+ context 'consisting of Range objects' do
133
+ let(:output) { klass.parse_input(valid_range_input)}
134
+
135
+ it 'where the collection is an Array' do
136
+ expect(output).to be_an_instance_of(Array)
137
+ end
138
+
139
+ it 'where the start points are StartPoint objects' do
140
+ expect(output.first).to be_an_instance_of(StartPoint)
141
+ end
142
+
143
+ it 'where the end points are EndPoint objects' do
144
+ expect(output.last).to be_an_instance_of(EndPoint)
145
+ end
146
+
147
+ it 'where the collection is sorted by the value attribute of each point' do
148
+ expect(output).to eq(output.sort_by {|point| point.value})
149
+ end
150
+
151
+ it 'where the id of the start points match the index of its originating range as found in the input array' do
152
+ expect(output.first.id).to eq(0)
153
+ end
154
+
155
+ it 'where the id of the end points match the index of its originating range as found in the input array' do
156
+ expect(output.last.id).to eq(0)
157
+ end
158
+ end
159
+ end
160
+
161
+ context 'when given invalid input' do
162
+ it 'raises an error' do
163
+ expect {klass.parse_input(['a', 1..5])}.to raise_error #Not sure if I need to specify an error type or if I can leave a general catch-all type 'raise_error' call.
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ module Overlaps
4
+ describe Overlap do
5
+ subject(:overlap) {Overlap.new(5,10,[1,2])}
6
+
7
+ it {should respond_to(:start_point)}
8
+ it {should respond_to(:end_point)}
9
+ it {should respond_to(:ids)}
10
+
11
+ its(:start_point) {should == 5}
12
+ its(:end_point) {should == 10}
13
+
14
+ its(:ids) {should be_an_instance_of(Array)}
15
+ its(:ids) {should == [1,2]}
16
+
17
+ its(:range) {should == (5..10)}
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ module Overlaps
4
+ describe Point do
5
+ subject {Point.new(10, 1)}
6
+
7
+ it {should respond_to(:value)}
8
+ it {should respond_to(:id)}
9
+
10
+ its(:value) {should == 10}
11
+ its(:id) {should == 1}
12
+
13
+ it {should_not be_start}
14
+ it {should_not be_end}
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ module Overlaps
4
+ describe StartPoint do
5
+ subject {StartPoint.new(10, 1)}
6
+
7
+ it {should respond_to(:value)}
8
+ it {should respond_to(:id)}
9
+
10
+ its(:value) {should == 10}
11
+ its(:id) {should == 1}
12
+
13
+ it {should be_start}
14
+ it {should_not be_end}
15
+ end
16
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Overlaps do
4
+ it 'has a version' do
5
+ expect(Overlaps::VERSION).to_not be_nil
6
+ end
7
+
8
+ it {should respond_to(:valid_input? )}
9
+ it {should respond_to(:grab_id)}
10
+ it {should respond_to(:convert_range_to_points)}
11
+ it {should respond_to(:parse_input)}
12
+
13
+ context 'included in a class' do
14
+ subject(:klass) {Class.new {include Overlaps}}
15
+
16
+ let(:date_ranges) {[Date.parse('2013-01-01')..Date.parse('2013-06-30'), Date.parse('2013-02-14')..Date.parse('2013-04-01'), Date.parse('2013-03-01')..Date.parse('2013-12-25'), Date.parse('2013-01-21')..Date.parse('2013-11-04')]}
17
+ let(:time_ranges) {[Time.parse('8:00AM')..Time.parse('8:00PM'), Time.parse('11:00AM')..Time.parse('5:30PM'), Time.parse('1:00AM')..Time.parse('12:45PM'), Time.parse('12:00PM')..Time.parse('11:00PM')]}
18
+ let(:float_ranges) {[(1.1..5.9), (3.3..7.1), (0.5..15.9), (6.6..16.6), (5.5..10.2)]}
19
+ let(:fixnum_ranges) {[(1..100), (25..55), (30..110), (150..175), (10..27)]}
20
+
21
+ it {should respond_to(:count_overlaps)}
22
+ it {should respond_to(:find_overlaps)}
23
+
24
+ context '#count_overlaps' do
25
+ let(:output) {klass.count_overlaps(fixnum_ranges)}
26
+ it 'returns a numeric count' do
27
+ expect(output).to be_an_instance_of(Fixnum)
28
+ end
29
+
30
+ context 'when given an array of ranges with 5 overlaps' do
31
+ it 'returns 5' do
32
+ expect(output).to eq(5)
33
+ end
34
+ end
35
+ end
36
+
37
+ context '#find_overlaps' do
38
+ context 'given an array of objects having start and end point attributes' do
39
+ context 'having 3 overlaps' do
40
+ let(:objects_with_range) {[ObjectWithRange.new(1, 10, 1), ObjectWithRange.new(1, 5, 2), ObjectWithRange.new(3, 6, 3)]}
41
+ let(:output) {klass.find_overlaps(objects_with_range, {start_attr: :start_point, end_attr: :end_point})}
42
+
43
+ it 'returns output in an array' do
44
+ expect(output).to be_an_instance_of(Array)
45
+ end
46
+
47
+ it 'returns a set of Overlap objects' do
48
+ expect(output.first).to be_an_instance_of(Overlaps::Overlap)
49
+ end
50
+
51
+ it 'returns 3 overlaps' do
52
+ expect(output.size).to eq(3)
53
+ end
54
+
55
+ it 'returns the correct set of overlaps' do
56
+ expect(output[0].range).to eq(3..5)
57
+ expect(output[0].ids).to match_array([1,2,3])
58
+ expect(output[1].range).to eq(1..5)
59
+ expect(output[1].ids).to match_array([1,2])
60
+ expect(output[2].range).to eq(3..6)
61
+ expect(output[2].ids).to match_array([1,3])
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,29 @@
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
+
8
+ require 'overlaps'
9
+
10
+ RSpec.configure do |config|
11
+ config.treat_symbols_as_metadata_keys_with_true_values = true
12
+ config.run_all_when_everything_filtered = true
13
+ #config.filter_run :focus
14
+
15
+ # Run specs in random order to surface order dependencies. If you find an
16
+ # order dependency and want to debug it, you can fix the order by providing
17
+ # the seed, which is printed after each run.
18
+ # --seed 1234
19
+ config.order = 'random'
20
+ end
21
+
22
+ class ObjectWithRange
23
+ attr_accessor :id, :start_point, :end_point
24
+ def initialize(s, e, i = nil)
25
+ self.id = i
26
+ self.start_point = s
27
+ self.end_point = e
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: overlaps
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Stenberg
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-22 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: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
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
+ description: Overlaps enables a class to either count or find overlaps between a set
42
+ of ranges.
43
+ email:
44
+ - h0tl33t@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - .gitignore
50
+ - .rspec
51
+ - Gemfile
52
+ - LICENSE.txt
53
+ - README.md
54
+ - Rakefile
55
+ - lib/overlaps.rb
56
+ - lib/overlaps/end_point.rb
57
+ - lib/overlaps/input_parser.rb
58
+ - lib/overlaps/overlap.rb
59
+ - lib/overlaps/point.rb
60
+ - lib/overlaps/start_point.rb
61
+ - lib/overlaps/version.rb
62
+ - overlaps.gemspec
63
+ - spec/overlaps/end_point_spec.rb
64
+ - spec/overlaps/input_parser_spec.rb
65
+ - spec/overlaps/overlap_spec.rb
66
+ - spec/overlaps/point_spec.rb
67
+ - spec/overlaps/start_point_spec.rb
68
+ - spec/overlaps_spec.rb
69
+ - spec/spec_helper.rb
70
+ homepage: https://github.com/h0tl33t/overlaps
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.0.6
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: 'Overlaps, when included in a class, enables that class to #count_overlaps
94
+ or #find_overlaps when given an array of Range objects or objects that have start
95
+ and end point-type attributes.'
96
+ test_files:
97
+ - spec/overlaps/end_point_spec.rb
98
+ - spec/overlaps/input_parser_spec.rb
99
+ - spec/overlaps/overlap_spec.rb
100
+ - spec/overlaps/point_spec.rb
101
+ - spec/overlaps/start_point_spec.rb
102
+ - spec/overlaps_spec.rb
103
+ - spec/spec_helper.rb