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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 47f94fb75dd165cd51d32e6e369a80523ae94772
4
- data.tar.gz: c7ad701a70c19715d40e81ca981b63014da3eeec
3
+ metadata.gz: 46b462c3111be6a7030d964585b9524a1a3a66d3
4
+ data.tar.gz: d83b354897aa8693341b732b4daca726413818d6
5
5
  SHA512:
6
- metadata.gz: efce43bad7f10d55ac8c6b76a1e43a31efbd99be2b9fb199320aa57a925520ddbf967ce0ab2e0bf780871ef86968aa9dc2f63677a4fd1aaff91cfb059c2858bd
7
- data.tar.gz: 93d01683a5c86dc4470836eb4a17c10512d8008df6b9d79be6ae5c5504cd9ec9e88a30397cf1892ddf167fcde5a2273b00a70fd7a11516a6dd97085777605637
6
+ metadata.gz: 86954a9d270c1075497cc8eb7adbb2d9e5ccc87e64960705d8ec4e515486cf9189b23534638e0881f1abd5ddcbed7be9c2139959a7f6cbf7d5c4cae53c5b0fe5
7
+ data.tar.gz: 415f604c26049c1140f59a31c39db17a87324c7757a55364be5a9b1723bd836af518612d0103d5593f24b37a2d5c9b1bcf16d73bf1cadd08c741ea5eb6592c84
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .DS_Store
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 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.
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 ranges -- is to find all possible overlaps and have information about which ranges belong to/share in a given overlap.
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
- 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).
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
- Klass.count_overlaps(array_of_ranges)
31
- Klass.find_overlaps(array_of_ranges)
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
- 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):
35
+ PseudoRange = Struct.new(:start_point, :end_point)
34
36
 
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')
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::ClassMethods::count_overlaps returns a Fixnum count:
41
+ Overlaps.count returns a Fixnum count:
47
42
 
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):
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 (zombies, where the Zombie class has included Overlaps) with :name, :date_of_birth, and :date_turned_zombie attributes that looked something like this:
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 find_overlaps like so:
78
-
79
- Zombie.find_overlaps(zombies, start_attr: :date_of_birth, end_attr: :date_turned_zombie, id_attr: :name)
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
- Klass.count_overlaps(zombies, start_attr: :date_of_birth, end_attr: :date_turned_zombie) => 3
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/input_parser'
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
- extend Overlaps::InputParser
12
-
13
- def self.included(base)
14
- base.extend(ClassMethods)
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
- 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
19
+ def self.count(ranges, accessors = {})
20
+ find(ranges, accessors).size
66
21
  end
67
22
  end
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Overlaps
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
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 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.}
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 {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
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(: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
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
@@ -2,15 +2,40 @@ require 'spec_helper'
2
2
 
3
3
  module Overlaps
4
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}
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 {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}
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
@@ -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
- 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
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.1
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-08-22 00:00:00.000000000 Z
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
- description: Overlaps enables a class to either count or find overlaps between a set
42
- of ranges.
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/input_parser_spec.rb
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.6
108
+ rubygems_version: 2.0.3
91
109
  signing_key:
92
110
  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.'
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/input_parser_spec.rb
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