overlaps 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +94 -0
- data/Rakefile +1 -0
- data/lib/overlaps/end_point.rb +9 -0
- data/lib/overlaps/input_parser.rb +46 -0
- data/lib/overlaps/overlap.rb +15 -0
- data/lib/overlaps/point.rb +18 -0
- data/lib/overlaps/start_point.rb +9 -0
- data/lib/overlaps/version.rb +3 -0
- data/lib/overlaps.rb +67 -0
- data/overlaps.gemspec +23 -0
- data/spec/overlaps/end_point_spec.rb +16 -0
- data/spec/overlaps/input_parser_spec.rb +168 -0
- data/spec/overlaps/overlap_spec.rb +19 -0
- data/spec/overlaps/point_spec.rb +16 -0
- data/spec/overlaps/start_point_spec.rb +16 -0
- data/spec/overlaps_spec.rb +67 -0
- data/spec/spec_helper.rb +29 -0
- metadata +103 -0
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
data/.rspec
ADDED
data/Gemfile
ADDED
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,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
|
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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|