Class: TimeIntervals::Collection

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Enumerable
Defined in:
lib/time_intervals/collection.rb

Constant Summary collapse

ONE_HOUR_IN_SECONDS =
60 * 60

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(time_intervals = []) ⇒ Collection

Returns a new instance of Collection.



22
23
24
# File 'lib/time_intervals/collection.rb', line 22

def initialize(time_intervals = [])
  @time_intervals = Array(time_intervals).sort
end

Instance Attribute Details

#time_intervalsObject (readonly)

Returns the value of attribute time_intervals.



15
16
17
# File 'lib/time_intervals/collection.rb', line 15

def time_intervals
  @time_intervals
end

Class Method Details

.wrap(intervals) ⇒ Object



17
18
19
20
# File 'lib/time_intervals/collection.rb', line 17

def self.wrap(intervals)
  time_intervals = intervals.map { |interval| Interval.new(interval.started_at, interval.ended_at) }
  new(time_intervals)
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



155
156
157
# File 'lib/time_intervals/collection.rb', line 155

def ==(other)
  other.class == self.class && other.time_intervals == time_intervals
end

#all_intervals_within?(bounding_interval) ⇒ Boolean

Returns true if all of the contained TimeIntervals::Intervals are wholly contained within the bounding interval.

Returns:

  • (Boolean)


28
29
30
# File 'lib/time_intervals/collection.rb', line 28

def all_intervals_within?(bounding_interval)
  length_in_seconds == intersect(bounding_interval).length_in_seconds
end

#coalesceObject

Returns a new coalesced collection of TimeIntervals::Intervals where any that overlap or are adjacent are combined into a single TimeIntervals::Interval.

Given these TimeIntervals::Intervals in the collection:

[——–)

[------)
       [-----)   [-------)
                      [-------)

Calling #coalesce returns a TimeIntervals::Collection containing these TimeIntervals::Intervals:

[——————) [————)



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/time_intervals/collection.rb', line 52

def coalesce
  return self if empty?

  coalescing = Interval.create(first)

  result = each_with_object([]) do |current, memo|
    if coalescing.ended_at < current.started_at
      memo << coalescing
      coalescing = Interval.create(current)
    else
      coalescing = Interval.new(
        coalescing.started_at,
        [coalescing.ended_at, current.ended_at].max
      )
    end
  end

  result << Interval.create(coalescing)

  Collection.new(result)
end

#has_overlapping_intervals?Boolean

Returns true if any of the contained TimeIntervals::Intervals overlap.

Returns:

  • (Boolean)


33
34
35
# File 'lib/time_intervals/collection.rb', line 33

def has_overlapping_intervals?
  length_in_seconds != coalesce.length_in_seconds
end

#intersect(intersections) ⇒ Object

Returns a new collection of TimeIntervals::Intervals that contains only intersections with the specified intersections: either a nil, a single TimeIntervals::Interval, or a TimeIntervals::Collection.

Note: the intersections are assumed to be disjoint. That is, none of the TimeIntervals::Intervals in intersections overlap.

Given these TimeIntervals::Intervals in the collection:

[——–)

[------)
       [-----)   [-------)
                      [-------)

Calling #intersect with these TimeIntervals::Intervals

[--------------) [------)

returns a TimeIntervals::Collection containing these TimeIntervals::Intervals:

[--)
   [-----)   [-) [---)
                  [-----)


98
99
100
101
102
103
104
# File 'lib/time_intervals/collection.rb', line 98

def intersect(intersections)
  result = Array(intersections).each_with_object([]) do |intersection, memo|
    memo.concat(intersect_with_time_interval(intersection))
  end

  Collection.new(result)
end

#intersect_count(intersections) ⇒ Object

Counts the number of TimeIntervals::Intervals that intersect with the given collection of TimeIntervals::Intervals.

Returns a list of [TimeIntervals::Interval, count] tuples. The TimeIntervals::Intervals in the result are the TimeIntervals::Intervals from the argument.



138
139
140
141
# File 'lib/time_intervals/collection.rb', line 138

def intersect_count(intersections)
  counts = intersections.map { |slice| intersect_with_time_interval(slice).length }
  intersections.zip(counts)
end

#length_in_hoursObject

The sum of the lengths of the TimeIntervals::Intervals in the collection as hours.



145
146
147
# File 'lib/time_intervals/collection.rb', line 145

def length_in_hours
  length_in_seconds.to_f / ONE_HOUR_IN_SECONDS
end

#length_in_secondsObject

The sum of the lengths of the TimeIntervals::Intervals in the collection as seconds.



151
152
153
# File 'lib/time_intervals/collection.rb', line 151

def length_in_seconds
  time_intervals.reduce(0) { |total, time_intervals| total + time_intervals.length_in_seconds }
end

#partitionObject

Returns a new collection of TimeIntervals::Intervals that are partitions of the original collection.

Given the upper TimeIntervals::Intervals, #partition returns the lower TimeIntervals::Intervals:

[——–) [—–) [—) | [——) | | | | | | [—-) | | | | | | | | | | | | | | | | | | | | | [—–) [-) [–) [—) |

[--) [-)  [--)   [---)


125
126
127
128
129
130
131
# File 'lib/time_intervals/collection.rb', line 125

def partition
  time_points = @time_intervals.flat_map { |i| [i.started_at, i.ended_at] }.uniq.sort
  start_time_points = time_points[0..-2]
  end_time_points = time_points[1..-1]
  raw_intervals = start_time_points.zip(end_time_points)
  Collection.new(raw_intervals.map { |r| Interval.new(*r) })
end

#partition_countObject



106
107
108
109
110
# File 'lib/time_intervals/collection.rb', line 106

def partition_count
  partition_intervals = partition

  intersect_count(partition_intervals)
end