overlaps 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rspec +2 -2
- data/README.md +34 -39
- data/lib/overlaps.rb +9 -54
- data/lib/overlaps/overlap.rb +2 -2
- data/lib/overlaps/overlap_factory.rb +64 -0
- data/lib/overlaps/point.rb +9 -3
- data/lib/overlaps/pseudo_range.rb +57 -0
- data/lib/overlaps/range_collection.rb +30 -0
- data/lib/overlaps/version.rb +1 -1
- data/overlaps.gemspec +3 -2
- data/spec/overlaps/end_point_spec.rb +15 -15
- data/spec/overlaps/overlap_factory_spec.rb +28 -0
- data/spec/overlaps/overlap_spec.rb +18 -18
- data/spec/overlaps/point_spec.rb +35 -10
- data/spec/overlaps/pseudo_range_spec.rb +118 -0
- data/spec/overlaps/range_collection_spec.rb +33 -0
- data/spec/overlaps/start_point_spec.rb +10 -10
- data/spec/overlaps_spec.rb +43 -66
- data/spec/spec_helper.rb +20 -29
- metadata +30 -11
- data/lib/overlaps/input_parser.rb +0 -46
- data/spec/overlaps/input_parser_spec.rb +0 -168
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46b462c3111be6a7030d964585b9524a1a3a66d3
|
4
|
+
data.tar.gz: d83b354897aa8693341b732b4daca726413818d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86954a9d270c1075497cc8eb7adbb2d9e5ccc87e64960705d8ec4e515486cf9189b23534638e0881f1abd5ddcbed7be9c2139959a7f6cbf7d5c4cae53c5b0fe5
|
7
|
+
data.tar.gz: 415f604c26049c1140f59a31c39db17a87324c7757a55364be5a9b1723bd836af518612d0103d5593f24b37a2d5c9b1bcf16d73bf1cadd08c741ea5eb6592c84
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
--color
|
2
|
-
--format progress
|
1
|
+
--color
|
2
|
+
--format progress
|
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# Overlaps
|
2
2
|
|
3
|
-
The Overlaps module
|
4
|
-
1. Range objects
|
5
|
-
2.
|
3
|
+
The Overlaps module provides methods to either find or count overlaps in an array of:
|
4
|
+
1. Range objects
|
5
|
+
2. Range-like objects that have start and end point-type attributes/values that form a valid range.
|
6
6
|
|
7
|
-
The main purpose of Overlaps -- given a set of
|
7
|
+
The main purpose of Overlaps -- given a set of range/range-like objects -- is to find all possible overlaps and have information about which ranges belong to/share in a given overlap.
|
8
8
|
|
9
9
|
## Installation
|
10
10
|
|
@@ -22,68 +22,63 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
|
-
|
25
|
+
Require the Overlaps module and call either #find or #count. You can feed them two main types of input:
|
26
|
+
|
27
|
+
Array of Range objects where all range start/end points are of the same class:
|
26
28
|
|
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
29
|
array_of_ranges = [1..100, 25..55, 30..110, 10..27]
|
30
|
-
|
31
|
-
|
30
|
+
Overlaps.find(array_of_ranges)
|
31
|
+
Overlaps.count(array_of_ranges)
|
32
|
+
|
33
|
+
Array of Range-like objects where each object is of the same class and the start and end point attribute/accessors are passed as values in an options hash to the :start and :end keys:
|
32
34
|
|
33
|
-
|
35
|
+
PseudoRange = Struct.new(:start_point, :end_point)
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
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')
|
37
|
+
array_of_objects = [ PseudoRange.new(1,10), PseudoRange.new(1,5), PseudoRange.new(3,6) ]
|
38
|
+
Overlaps.find(array_of_objects, start: :start_point, end: :end_point)
|
39
|
+
Overlaps.count(array_of_objects, start: :start_point, end: :end_point)
|
45
40
|
|
46
|
-
Overlaps
|
41
|
+
Overlaps.count returns a Fixnum count:
|
47
42
|
|
48
|
-
|
49
|
-
|
50
|
-
Overlaps
|
43
|
+
Overlaps.count(array_of_ranges) => 5
|
44
|
+
|
45
|
+
Overlaps.find returns an array of Overlap objects, where each overlap captures the following (with example output):
|
51
46
|
|
52
47
|
Start Point Value
|
53
|
-
|
48
|
+
|
54
49
|
overlap.start_point => 1
|
55
|
-
|
50
|
+
|
56
51
|
End Point Value
|
57
|
-
|
52
|
+
|
58
53
|
overlap.end_point => 5
|
59
|
-
|
54
|
+
|
60
55
|
Range
|
61
|
-
|
56
|
+
|
62
57
|
overlap.range => 1..5
|
63
|
-
|
58
|
+
|
64
59
|
IDs of Each Range that Shares the Overlap
|
65
|
-
|
60
|
+
|
66
61
|
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.
|
62
|
+
|
63
|
+
Also, you may pass a specific attribute to use as the id (assign the id attr/accessor to the :id key in the options hash). 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
64
|
|
70
65
|
## Example Use Case
|
71
|
-
Say we had a list of Zombie objects
|
66
|
+
Say we had a list of Zombie objects with :name, :date_of_birth, and :date_turned_zombie attributes that looked something like this:
|
72
67
|
|
73
68
|
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
69
|
#<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
70
|
#<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
71
|
|
77
|
-
If we wanted to find the overlapping periods where each of them were alive and not yet a zombie, we could call
|
78
|
-
|
79
|
-
|
72
|
+
If we wanted to find the overlapping periods where each of them were alive and not yet a zombie, we could call Overlaps.find like so:
|
73
|
+
|
74
|
+
Overlaps.find(zombies, start: :date_of_birth, end: :date_turned_zombie, id: :name)
|
80
75
|
=> [#<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
76
|
#<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
77
|
#<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
78
|
|
84
79
|
If we simply wanted to count the number of overlapping periods, we could call count_overlaps like so:
|
85
|
-
|
86
|
-
|
80
|
+
|
81
|
+
Overlaps.count(zombies, start: :date_of_birth, end: :date_turned_zombie) => 3
|
87
82
|
|
88
83
|
## Contributing
|
89
84
|
|
data/lib/overlaps.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require_relative 'overlaps/version'
|
2
|
-
require_relative 'overlaps/
|
2
|
+
require_relative 'overlaps/range_collection'
|
3
|
+
require_relative 'overlaps/overlap_factory'
|
4
|
+
require_relative 'overlaps/pseudo_range'
|
3
5
|
require_relative 'overlaps/start_point'
|
4
6
|
require_relative 'overlaps/end_point'
|
5
7
|
require_relative 'overlaps/overlap'
|
@@ -8,60 +10,13 @@ require 'date'
|
|
8
10
|
require 'time'
|
9
11
|
|
10
12
|
module Overlaps
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
|
14
|
+
def self.find(ranges, accessors = {})
|
15
|
+
range_collection = Overlaps::RangeCollection.new(ranges, accessors)
|
16
|
+
Overlaps::OverlapFactory.new(range_collection.points).overlaps
|
15
17
|
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
18
|
|
39
|
-
|
40
|
-
|
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
|
19
|
+
def self.count(ranges, accessors = {})
|
20
|
+
find(ranges, accessors).size
|
66
21
|
end
|
67
22
|
end
|
data/lib/overlaps/overlap.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
module Overlaps
|
2
2
|
class Overlap
|
3
3
|
attr_accessor :start_point, :end_point, :ids
|
4
|
-
|
4
|
+
|
5
5
|
def initialize(start_point, end_point, ids)
|
6
6
|
@start_point = start_point
|
7
7
|
@end_point = end_point
|
8
8
|
@ids = ids
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def range
|
12
12
|
(start_point..end_point)
|
13
13
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Overlaps
|
2
|
+
class OverlapFactory
|
3
|
+
|
4
|
+
def initialize(points)
|
5
|
+
@count = -1
|
6
|
+
@points = Array(points)
|
7
|
+
@overlaps = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def overlaps
|
11
|
+
build_overlaps_with_points
|
12
|
+
@overlaps
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def build_overlaps_with_points
|
18
|
+
@points.each { |point| process(point) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def start_points
|
22
|
+
@start_points ||= []
|
23
|
+
end
|
24
|
+
|
25
|
+
def process(point)
|
26
|
+
if point.start?
|
27
|
+
store_start_point(point)
|
28
|
+
elsif point.end?
|
29
|
+
store_end_point(point)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def store_start_point(point)
|
34
|
+
start_points << point
|
35
|
+
@count += 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def store_end_point(point)
|
39
|
+
build_overlap(point)
|
40
|
+
close_out_range(point)
|
41
|
+
@count -= 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def start_ids
|
45
|
+
start_points.map(&:id)
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_overlap(end_point)
|
49
|
+
stack_of_start_points = start_points.dup
|
50
|
+
ids = start_ids
|
51
|
+
|
52
|
+
@count.times do
|
53
|
+
start_point = stack_of_start_points.pop
|
54
|
+
@overlaps << Overlaps::Overlap.new(start_point.value, end_point.value, ids.dup)
|
55
|
+
break if start_point.id == end_point.id
|
56
|
+
ids.pop
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def close_out_range(end_point)
|
61
|
+
start_points.delete_if {|start| start.id == end_point.id}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/overlaps/point.rb
CHANGED
@@ -1,18 +1,24 @@
|
|
1
1
|
module Overlaps
|
2
2
|
class Point
|
3
|
+
include Comparable
|
4
|
+
|
3
5
|
attr_accessor :value, :id
|
4
|
-
|
6
|
+
|
5
7
|
def initialize(value, id)
|
6
8
|
@value = value
|
7
9
|
@id = id
|
8
10
|
end
|
9
|
-
|
11
|
+
|
10
12
|
def start?
|
11
13
|
false
|
12
14
|
end
|
13
|
-
|
15
|
+
|
14
16
|
def end?
|
15
17
|
false
|
16
18
|
end
|
19
|
+
|
20
|
+
def <=>(other_point)
|
21
|
+
value <=> other_point.value
|
22
|
+
end
|
17
23
|
end
|
18
24
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Overlaps
|
2
|
+
class PseudoRange
|
3
|
+
attr_accessor :start_point, :end_point, :id
|
4
|
+
|
5
|
+
def initialize(range, accessors = {})
|
6
|
+
verify_input(range, accessors)
|
7
|
+
ensure_points_are_same_class
|
8
|
+
build
|
9
|
+
end
|
10
|
+
|
11
|
+
def points
|
12
|
+
[@start_point, @end_point]
|
13
|
+
end
|
14
|
+
|
15
|
+
def range
|
16
|
+
(@start_point.value..@end_point.value)
|
17
|
+
end
|
18
|
+
|
19
|
+
def type
|
20
|
+
@start_point.value.class
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def verify_input(range, accessors)
|
26
|
+
if range.is_a?(Range)
|
27
|
+
@start_point = range.first
|
28
|
+
@end_point = range.last
|
29
|
+
@id = accessors.fetch(:index) { raise ArgumentError, 'Must provide the :index key/value option when given a Range object.' }
|
30
|
+
else
|
31
|
+
@start_point = range.send accessors.fetch(:start) { raise ArgumentError, 'Must provide the :start key/value option when given a non-Range object.' }
|
32
|
+
@end_point = range.send accessors.fetch(:end) { raise ArgumentError, 'Must provide the :end key/value option when given a non-Range object.' }
|
33
|
+
@id = determine_id(range, accessors)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def determine_id(range, accessors)
|
38
|
+
method = accessors[:id]
|
39
|
+
if method && range.respond_to?(method)
|
40
|
+
range.send(method)
|
41
|
+
elsif range.respond_to?(:id)
|
42
|
+
range.send(:id)
|
43
|
+
else
|
44
|
+
accessors.fetch(:index)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def ensure_points_are_same_class
|
49
|
+
raise TypeError, 'The start and end points for a PseudoRange must be of the same class.' unless start_point.class == end_point.class
|
50
|
+
end
|
51
|
+
|
52
|
+
def build
|
53
|
+
@start_point = StartPoint.new(@start_point, @id)
|
54
|
+
@end_point = EndPoint.new(@end_point, @id)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Overlaps
|
2
|
+
class RangeCollection
|
3
|
+
|
4
|
+
def initialize(range_input, accessors)
|
5
|
+
build(range_input, accessors)
|
6
|
+
ensure_single_class_among_points
|
7
|
+
end
|
8
|
+
|
9
|
+
def ranges
|
10
|
+
@ranges ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
def points
|
14
|
+
ranges.map(&:points).flatten.sort
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def build(range_input, accessors)
|
20
|
+
range_input.each_with_index do |range, index|
|
21
|
+
accessors[:index] = index
|
22
|
+
ranges << PseudoRange.new(range, accessors)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def ensure_single_class_among_points
|
27
|
+
raise TypeError, 'All ranges must have a single shared class of start/end point.' unless ranges.map(&:type).uniq.size == 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/overlaps/version.rb
CHANGED
data/overlaps.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Overlaps::VERSION
|
9
9
|
spec.authors = ["Ryan Stenberg"]
|
10
10
|
spec.email = ["h0tl33t@gmail.com"]
|
11
|
-
spec.description = %q{Overlaps
|
12
|
-
spec.summary = %q{Overlaps
|
11
|
+
spec.description = %q{Overlaps aids in finding shared overlaps between a set of Range or Range-like objects.}
|
12
|
+
spec.summary = %q{Overlaps lets you #find or #count overlaps in a given set of Range or Range-like objects.}
|
13
13
|
spec.homepage = "https://github.com/h0tl33t/overlaps"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -20,4 +20,5 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
22
|
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
23
24
|
end
|
@@ -1,16 +1,16 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
module Overlaps
|
4
|
-
describe EndPoint do
|
5
|
-
subject {
|
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
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Overlaps
|
4
|
+
describe EndPoint do
|
5
|
+
subject { described_class.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
16
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Overlaps
|
4
|
+
describe OverlapFactory do
|
5
|
+
|
6
|
+
describe "#overlaps" do
|
7
|
+
let(:point1) { double 'point', start?: true, end?: false, id: 0, value: 1 }
|
8
|
+
let(:point2) { double 'point', start?: true, end?: false, id: 1, value: 2 }
|
9
|
+
let(:point3) { double 'point', start?: true, end?: false, id: 2, value: 3 }
|
10
|
+
let(:point4) { double 'point', start?: false, end?: true, id: 2, value: 6 }
|
11
|
+
let(:point5) { double 'point', start?: false, end?: true, id: 1, value: 8 }
|
12
|
+
let(:point6) { double 'point', start?: false, end?: true, id: 0, value: 9}
|
13
|
+
let(:points) { [point1, point2, point3, point4, point5, point6] }
|
14
|
+
let(:overlap1) { double 'overlap1' }
|
15
|
+
let(:overlap2) { double 'overlap2' }
|
16
|
+
subject { described_class.new(points) }
|
17
|
+
|
18
|
+
before do
|
19
|
+
Overlap.stub(:new).with(3, 6, [0,1,2,]).and_return(overlap1)
|
20
|
+
Overlap.stub(:new).with(2, 8, [0,1]).and_return(overlap2)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns the correct set of overlaps" do
|
24
|
+
subject.overlaps.should == [overlap1, overlap2]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,19 +1,19 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
module Overlaps
|
4
|
-
describe Overlap do
|
5
|
-
subject
|
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
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Overlaps
|
4
|
+
describe Overlap do
|
5
|
+
subject { described_class.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
19
|
end
|
data/spec/overlaps/point_spec.rb
CHANGED
@@ -2,15 +2,40 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Overlaps
|
4
4
|
describe Point do
|
5
|
-
subject {
|
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}
|
5
|
+
subject { described_class.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
|
+
|
16
|
+
describe "#<=>" do
|
17
|
+
context "when passed a point with a lesser value" do
|
18
|
+
let(:point) { described_class.new(5,1) }
|
19
|
+
it "returns 1" do
|
20
|
+
(subject <=> point).should == 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when passed a point with equal value" do
|
25
|
+
let(:point) { described_class.new(10,1) }
|
26
|
+
|
27
|
+
it "returns 0" do
|
28
|
+
(subject <=> point).should == 0
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when passed a point of greater value" do
|
33
|
+
let(:point) { described_class.new(20, 1) }
|
34
|
+
|
35
|
+
it "returns -1" do
|
36
|
+
(subject <=> point).should == -1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
15
40
|
end
|
16
41
|
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Overlaps
|
4
|
+
describe PseudoRange do
|
5
|
+
|
6
|
+
let(:range) { 1..100 }
|
7
|
+
let(:accessors) { {index: 5} }
|
8
|
+
subject { described_class.new(range, accessors) }
|
9
|
+
|
10
|
+
context "when initialized with a Range object" do
|
11
|
+
context "without an index in the accompanying hash" do
|
12
|
+
it "raises an error" do
|
13
|
+
expect { described_class.new(range) }.to raise_error(ArgumentError)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "with an index in the accompanying hash" do
|
18
|
+
subject { described_class.new(range, accessors) }
|
19
|
+
|
20
|
+
it "creates a start_point of class Overlaps::StartPoint" do
|
21
|
+
subject.start_point.class == Overlaps::StartPoint
|
22
|
+
end
|
23
|
+
|
24
|
+
it "creates a start point with the index as the id" do
|
25
|
+
subject.start_point.id == 5
|
26
|
+
end
|
27
|
+
|
28
|
+
it "creates an end_point of class Overlaps::EndPoint" do
|
29
|
+
subject.end_point.class == Overlaps::EndPoint
|
30
|
+
end
|
31
|
+
|
32
|
+
it "creates an end point with the index as the id" do
|
33
|
+
subject.end_point.id == 5
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when initialized with a non-Range object" do
|
39
|
+
let(:range) { double 'range', start_point: 1, end_point: 100, id: 5 }
|
40
|
+
|
41
|
+
context "without a start accessor in the accompanying hash" do
|
42
|
+
let(:accessors) { {end: :end_point, id: :id} }
|
43
|
+
|
44
|
+
it "raises an error" do
|
45
|
+
expect { described_class.new(range, accessors) }.to raise_error(ArgumentError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "without an end accessor in the accompanying hash" do
|
50
|
+
let(:accessors) { {start: :start_point, id: :id} }
|
51
|
+
|
52
|
+
it "raises an error" do
|
53
|
+
expect { described_class.new(range, accessors) }.to raise_error(ArgumentError)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "where the start and end points are of a different class" do
|
58
|
+
let(:range) { double 'range', start_point: 1, end_point: 'z', id: 5 }
|
59
|
+
let(:accessors) { {start: :start_point, end: :end_point, id: :id} }
|
60
|
+
|
61
|
+
it "raises an error" do
|
62
|
+
expect { described_class.new(range, accessors) }.to raise_error(TypeError)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "with valid input" do
|
67
|
+
let(:accessors) { {start: :start_point, end: :end_point, id: :id} }
|
68
|
+
subject { described_class.new(range, accessors) }
|
69
|
+
|
70
|
+
context "creates a start point" do
|
71
|
+
it "of class Overlaps::StartPoint" do
|
72
|
+
subject.start_point.class.should == Overlaps::StartPoint
|
73
|
+
end
|
74
|
+
|
75
|
+
it "with the start value of the original range as determined by the start accessor" do
|
76
|
+
subject.start_point.value.should == 1
|
77
|
+
end
|
78
|
+
|
79
|
+
it "with the id from the original object as determined by the id accessor" do
|
80
|
+
subject.start_point.id.should == 5
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "creates an end point" do
|
85
|
+
it "of class Overlaps::EndPoint" do
|
86
|
+
subject.end_point.class.should == Overlaps::EndPoint
|
87
|
+
end
|
88
|
+
|
89
|
+
it "with the start value of the original range as determined by the start accessor" do
|
90
|
+
subject.end_point.value.should == 100
|
91
|
+
end
|
92
|
+
|
93
|
+
it "with the id from the original object as determined by the id accessor" do
|
94
|
+
subject.end_point.id.should == 5
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "#points" do
|
101
|
+
it "returns an array containing the start and end point" do
|
102
|
+
subject.points.should == [subject.start_point, subject.end_point]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "#range" do
|
107
|
+
it "returns a Range using the start and end point values" do
|
108
|
+
subject.range.should == (1..100)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#type" do
|
113
|
+
it "returns the class of the start and end point values" do
|
114
|
+
subject.type.should == Fixnum
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Overlaps
|
4
|
+
describe RangeCollection do
|
5
|
+
|
6
|
+
let(:ranges) { [1..100, 25..75, 50..90] }
|
7
|
+
let(:accessors) { Hash.new }
|
8
|
+
let(:pseudo_range) { double('pseudo_range', points: [1,5], type: :cool) }
|
9
|
+
subject { described_class.new(ranges, accessors) }
|
10
|
+
|
11
|
+
context "when initialized" do
|
12
|
+
it "converts each of the passed ranges to PseudoRange objects before storing" do
|
13
|
+
PseudoRange.stub(new: pseudo_range)
|
14
|
+
PseudoRange.should_receive(:new).exactly(ranges.size).times
|
15
|
+
described_class.new(ranges, accessors)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#ranges" do
|
20
|
+
it "returns an array of PseudoRange objects" do
|
21
|
+
subject.ranges.map(&:class).uniq.should == [PseudoRange]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#points" do
|
26
|
+
it "returns a sorted list of points" do
|
27
|
+
PseudoRange.stub(new: pseudo_range)
|
28
|
+
pseudo_range.should_receive(:points).exactly(ranges.size).times
|
29
|
+
subject.points.should == [1,1,1,5,5,5]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -2,15 +2,15 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Overlaps
|
4
4
|
describe StartPoint do
|
5
|
-
subject {
|
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}
|
5
|
+
subject { described_class.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
15
|
end
|
16
16
|
end
|
data/spec/overlaps_spec.rb
CHANGED
@@ -1,67 +1,44 @@
|
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
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
|
+
describe "#find" do
|
9
|
+
let(:ranges) { [1..10, 3..6] }
|
10
|
+
let(:options) { Hash.new }
|
11
|
+
let(:overlaps) { 'overlaps' }
|
12
|
+
let(:overlap_factory) { double 'overlap_factory', overlaps: overlaps}
|
13
|
+
let(:range_collection) { double 'range_collection', points: 'PONITZ!' }
|
14
|
+
let(:overlaps) { double 'overlaps' }
|
15
|
+
|
16
|
+
it "it creates a RangeCollection with provided input" do
|
17
|
+
Overlaps::OverlapFactory.stub(:new => overlap_factory )
|
18
|
+
Overlaps::OverlapFactory.stub(:overlaps)
|
19
|
+
Overlaps::RangeCollection.should_receive(:new).with(ranges, options).and_return(range_collection)
|
20
|
+
subject.find(ranges, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "it makes an OverlapsFactory with that RangeCollection" do
|
24
|
+
Overlaps::RangeCollection.stub(:new).with(ranges, options).and_return(range_collection)
|
25
|
+
Overlaps::OverlapFactory.should_receive(:new).with(range_collection.points).and_return(overlap_factory)
|
26
|
+
subject.find(ranges, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "it calls overlaps on the created OverlapFactory" do
|
30
|
+
Overlaps::RangeCollection.stub(:new).with(ranges, options).and_return(range_collection)
|
31
|
+
Overlaps::OverlapFactory.stub(:new).with(range_collection.points).and_return(overlap_factory)
|
32
|
+
overlap_factory.should_receive(:overlaps).and_return(overlaps)
|
33
|
+
subject.find(ranges, options)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#count" do
|
38
|
+
let(:ranges) { [1..100, 25..75] }
|
39
|
+
|
40
|
+
it "returns the number of overlaps found" do
|
41
|
+
subject.count(ranges).should == 1
|
42
|
+
end
|
43
|
+
end
|
67
44
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,29 +1,20 @@
|
|
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
|
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
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: overlaps
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Stenberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,8 +38,22 @@ dependencies:
|
|
38
38
|
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
-
|
42
|
-
|
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: Overlaps aids in finding shared overlaps between a set of Range or Range-like
|
56
|
+
objects.
|
43
57
|
email:
|
44
58
|
- h0tl33t@gmail.com
|
45
59
|
executables: []
|
@@ -54,16 +68,20 @@ files:
|
|
54
68
|
- Rakefile
|
55
69
|
- lib/overlaps.rb
|
56
70
|
- lib/overlaps/end_point.rb
|
57
|
-
- lib/overlaps/input_parser.rb
|
58
71
|
- lib/overlaps/overlap.rb
|
72
|
+
- lib/overlaps/overlap_factory.rb
|
59
73
|
- lib/overlaps/point.rb
|
74
|
+
- lib/overlaps/pseudo_range.rb
|
75
|
+
- lib/overlaps/range_collection.rb
|
60
76
|
- lib/overlaps/start_point.rb
|
61
77
|
- lib/overlaps/version.rb
|
62
78
|
- overlaps.gemspec
|
63
79
|
- spec/overlaps/end_point_spec.rb
|
64
|
-
- spec/overlaps/
|
80
|
+
- spec/overlaps/overlap_factory_spec.rb
|
65
81
|
- spec/overlaps/overlap_spec.rb
|
66
82
|
- spec/overlaps/point_spec.rb
|
83
|
+
- spec/overlaps/pseudo_range_spec.rb
|
84
|
+
- spec/overlaps/range_collection_spec.rb
|
67
85
|
- spec/overlaps/start_point_spec.rb
|
68
86
|
- spec/overlaps_spec.rb
|
69
87
|
- spec/spec_helper.rb
|
@@ -87,17 +105,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
105
|
version: '0'
|
88
106
|
requirements: []
|
89
107
|
rubyforge_project:
|
90
|
-
rubygems_version: 2.0.
|
108
|
+
rubygems_version: 2.0.3
|
91
109
|
signing_key:
|
92
110
|
specification_version: 4
|
93
|
-
summary: 'Overlaps
|
94
|
-
|
95
|
-
and end point-type attributes.'
|
111
|
+
summary: 'Overlaps lets you #find or #count overlaps in a given set of Range or Range-like
|
112
|
+
objects.'
|
96
113
|
test_files:
|
97
114
|
- spec/overlaps/end_point_spec.rb
|
98
|
-
- spec/overlaps/
|
115
|
+
- spec/overlaps/overlap_factory_spec.rb
|
99
116
|
- spec/overlaps/overlap_spec.rb
|
100
117
|
- spec/overlaps/point_spec.rb
|
118
|
+
- spec/overlaps/pseudo_range_spec.rb
|
119
|
+
- spec/overlaps/range_collection_spec.rb
|
101
120
|
- spec/overlaps/start_point_spec.rb
|
102
121
|
- spec/overlaps_spec.rb
|
103
122
|
- spec/spec_helper.rb
|
@@ -1,46 +0,0 @@
|
|
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
|
@@ -1,168 +0,0 @@
|
|
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
|