timed 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 91f02760afcbcaf91d29ddf7fe817fbc0ec7dcf5
4
- data.tar.gz: b7220abee0fce727c26f6961ebbffbc737cc7eb2
3
+ metadata.gz: 51ecc6d39d16ae5d47b2b658519e10bf6eb16098
4
+ data.tar.gz: e6f32281f459d65b349774944625d7232c95654b
5
5
  SHA512:
6
- metadata.gz: db98749f2945a92b8ae56e58bdeb08d05c4b417fe8e2d9fc627f9de9cf39becf99000189c9b32b3eda704299eb8ac045ab23ba5f42e96191744534559f828c4d
7
- data.tar.gz: 076b4b45416d418b1554453f5a97b82c717a242cbff240c827111a527bb0ac24e74b822d03498be917a020f84e035fe4cdec325b8d44b44c8db9e49f96abf97e
6
+ metadata.gz: ceb78b7e82c7f1ab1de38cd7747a681261906d3b8517324a26245f899346cbbd749cec81e6bd54fdadf34d21b1ea0095d27ddf8c39148fc0693c3a5d95aa4901
7
+ data.tar.gz: d97ed62d7ca6e1c787749b824cdfd8c64790b8662020ba7cfde66b240a4e9dd9567476f968cc25be83412538871ac62247447eae2c23e9a7c11fafc2728ca4b2
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Timed
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/timed.svg)](https://badge.fury.io/rb/timed)
3
4
  [![Build Status](https://travis-ci.org/seblindberg/ruby-timed.svg?branch=master)](https://travis-ci.org/seblindberg/ruby-timed)
4
5
  [![Coverage Status](https://coveralls.io/repos/github/seblindberg/ruby-timed/badge.svg?branch=master)](https://coveralls.io/github/seblindberg/ruby-timed?branch=master)
5
6
  [![Inline docs](http://inch-ci.org/github/seblindberg/ruby-timed.svg?branch=master)](http://inch-ci.org/github/seblindberg/ruby-timed)
data/lib/timed/item.rb CHANGED
@@ -4,15 +4,22 @@ module Timed
4
4
  # The Timed Item is a Moment that can be chained to others to form a sequence.
5
5
  # Importantly, items in a sequence are guaranteed to be sequential and non
6
6
  # overlapping.
7
-
7
+
8
8
  class Item < Linked::Item
9
9
  include Moment
10
+
10
11
  # The value field is used to store the timespan and shuold not be accessed
11
12
  # directly.
12
13
 
13
14
  protected :value
14
15
  private :value=
15
16
 
17
+ # Provide a more ideomatic accessor for the sequence that the item is part
18
+ # of.
19
+
20
+ alias sequence list
21
+ alias in_sequence? in_list?
22
+
16
23
  # Creates a new Timed Item from a timespan. A timespan is any object that
17
24
  # responds to #begin and #end with two numerical values. Note that the end
18
25
  # must occur after the start for the span to be valid.
@@ -22,9 +29,10 @@ module Timed
22
29
  #
23
30
  # timespan - object responding to #begin and #end.
24
31
  # end_at - if given, it togheter with the first argument will be used as the
25
- # begin and end time for the item
32
+ # begin and end time for the item.
33
+ # sequence - optional sequence to add the item to.
26
34
 
27
- def initialize(timespan, end_at = nil)
35
+ def initialize(timespan, end_at = nil, sequence: nil)
28
36
  if end_at
29
37
  begin_at = timespan
30
38
  else
@@ -35,34 +43,21 @@ module Timed
35
43
  raise TypeError unless begin_at.is_a?(Numeric) && end_at.is_a?(Numeric)
36
44
  raise ArgumentError if end_at < begin_at
37
45
 
38
- super begin_at..end_at
46
+ super begin_at..end_at, list: sequence
39
47
  end
40
48
 
41
49
  # Returns the time when the item starts.
42
50
 
43
51
  def begin
44
- value.begin
52
+ offset value.begin
45
53
  end
46
54
 
47
55
  # Returns the time when the item ends.
48
56
 
49
57
  def end
50
- value.end
51
- end
52
-
53
- # Returns a new Item in the intersection
54
- #
55
- # other - object that implements both #begin and #end.
56
-
57
- def intersect(other)
58
- begin_at = self.begin >= other.begin ? self.begin : other.begin
59
- end_at = self.end <= other.end ? self.end : other.end
60
-
61
- begin_at <= end_at ? self.class.new(begin_at, end_at) : nil
58
+ offset value.end
62
59
  end
63
60
 
64
- alias & intersect
65
-
66
61
  # Inserts an item after this one and before the next in the sequence. The
67
62
  # new item may not overlap with the two it sits between. A RuntimeError will
68
63
  # be raised if it does.
@@ -92,5 +87,9 @@ module Timed
92
87
 
93
88
  super
94
89
  end
90
+
91
+ protected def offset(time)
92
+ in_sequence? ? sequence.offset(time) : time
93
+ end
95
94
  end
96
95
  end
data/lib/timed/moment.rb CHANGED
@@ -81,19 +81,6 @@ module Timed
81
81
 
82
82
  alias & intersect
83
83
 
84
- # Compare the moments with others.
85
- #
86
- # Return -1 if the item is before the other, 0 if they overlap and 1 if the
87
- # item is after the other.
88
-
89
- # def <=>(other)
90
- # case
91
- # when before?(other) then -1
92
- # when after?(other) then 1
93
- # else 0
94
- # end
95
- # end
96
-
97
84
  # Override the default implementation and provide a more useful
98
85
  # representation of the Timed Moment, including when it begins and ends.
99
86
  #
@@ -2,8 +2,8 @@ module Timed
2
2
  # Sequence
3
3
  #
4
4
  # This class implements a sequence of Timed Items. Any object that implements
5
- # the methods #begin and #end can be push to the sequence. Note that the items
6
- # must be inserted in chronological order, or the sequence will raise an
5
+ # the methods #begin and #end can be added to the sequence. Note that the
6
+ # items must be inserted in chronological order, or the sequence will raise an
7
7
  # exception.
8
8
  #
9
9
  # Example
@@ -13,11 +13,19 @@ module Timed
13
13
  #
14
14
  # A sequence can also be treated like a Moment and be compared, in time, with
15
15
  # other compatible objects.
16
-
16
+ #
17
+ # Sequences also provide a mechanism to offset the items in it, in time by
18
+ # providing the #offset method. Items can use it to offset their begin and end
19
+ # times on the fly.
20
+
17
21
  class Sequence
18
22
  include Moment
19
23
  include Linked::List
20
24
 
25
+ # Provide a more ideomatic name for the identity method #list.
26
+
27
+ alias sequence list
28
+
21
29
  # Returns the time at which the first item in the sequence, and therefore
22
30
  # the sequence as a whole, begins. If the sequence is empty, by convention,
23
31
  # it both begins and ends at time 0, giving it a 0 length.
@@ -76,6 +84,96 @@ module Timed
76
84
  end
77
85
  end
78
86
 
87
+ # Offset the entire sequence by specifying the coefficients of a polynomial
88
+ # of up to degree 2. This is then used to recalculate the begin and end
89
+ # times of each item in the set. The operation does not change the items but
90
+ # is instead performed on the fly every time either #begin or #end is
91
+ # called.
92
+ #
93
+ # Example
94
+ # sequence.offset_by 10, 1.1, 0.01
95
+ # ^ ^ ^ Quadratic term
96
+ # | + Linear term
97
+ # + Constant term
98
+ # sequence.offset 42.0 # => 73.84
99
+ #
100
+ # c - list of coefficients, starting with the constant term and ending with,
101
+ # at most, the quadratic.
102
+
103
+ def offset_by(*c)
104
+ body =
105
+ case c.length
106
+ when 0 then proc { |t| t }
107
+ when 1
108
+ if c[0] == 0 || !c[0]
109
+ proc { |t| t }
110
+ else
111
+ proc { |t| c[0] + t }
112
+ end
113
+ when 2 then proc { |t| c[0] + c[1] * t }
114
+ when 3 then proc { |t| c[0] + c[1] * t + c[2] * t**2 }
115
+ else
116
+ raise ArgumentError,
117
+ 'Only polynomilas of order 2 or lower are supported'
118
+ end
119
+
120
+ redefine_method :offset, body
121
+ end
122
+
123
+ alias offset= offset_by
124
+
125
+ # Offset any time using the current offset settings of the sequence. Note
126
+ # that this method is overridden everytime #offset_by is called.
127
+ #
128
+ # time - the time to be offset.
129
+ #
130
+ # Returns the offset time.
131
+
132
+ def offset(time)
133
+ time
134
+ end
135
+
136
+ # Iterate over all of the edges in the sequence. An edge is the point in
137
+ # time when an item either begins or ends. That time, a numeric value, will
138
+ # be yielded to the block. If a block is not given and enumerator is
139
+ # returned.
140
+ #
141
+ # Returns an enumerator if a block was not given.
142
+
143
+ def each_edge
144
+ return to_enum __callee__ unless block_given?
145
+
146
+ each_item do |item|
147
+ yield item.begin
148
+ yield item.end
149
+ end
150
+ end
151
+
152
+ # Iterates over all the leading edges in the sequence. A leading edge is the
153
+ # point in time where an item begins. That time, a numeric value, will be
154
+ # yielded to the block. If a block is not given and enumerator is returned.
155
+ #
156
+ # Returns an enumerator if a block was not given.
157
+
158
+ def each_leading_edge
159
+ return to_enum __callee__ unless block_given?
160
+
161
+ each_item { |item| yield item.begin }
162
+ end
163
+
164
+ # Iterates over all the trailing edges in the sequence. A trailing edge is
165
+ # the point in time where an item begins. That time, a numeric value, will
166
+ # be yielded to the block. If a block is not given and enumerator is
167
+ # returned.
168
+ #
169
+ # Returns an enumerator if a block was not given.
170
+
171
+ def each_trailing_edge
172
+ return to_enum __callee__ unless block_given?
173
+
174
+ each_item { |item| yield item.end }
175
+ end
176
+
79
177
  # This method takes a second sequence and iterates over each intersection
80
178
  # between the two. If a block is given, the beginning and end of each
81
179
  # intersecting period will be yielded to it. Otherwise an enumerator is
@@ -84,10 +182,9 @@ module Timed
84
182
  # other - a sequence, or object that responds to #begin and #end and returns
85
183
  # a Timed::Item in response to #first
86
184
  #
87
- # Returns an enumerator unless a block is given, in which case the number of
88
- # intersections is returned.
185
+ # Returns an enumerator unless a block is given.
89
186
 
90
- def intersections(other)
187
+ def intersections(other, &block)
91
188
  return to_enum __callee__, other unless block_given?
92
189
 
93
190
  return unless during? other
@@ -95,16 +192,14 @@ module Timed
95
192
  # Sort the first items from each sequence into leading
96
193
  # and trailing by whichever begins first
97
194
  if self.begin <= other.begin
98
- item_l, item_t = first, other.first
195
+ item_l, item_t = self.item, other.item
99
196
  else
100
- item_l, item_t = other.first, first
197
+ item_l, item_t = other.item, self.item
101
198
  end
102
199
 
103
- count = 0
104
-
105
200
  loop do
106
201
  # Now there are three posibilities:
107
-
202
+
108
203
  # 1: The leading item ends before the trailing one
109
204
  # begins. In this case the items do not intersect
110
205
  # at all and we do nothing.
@@ -114,12 +209,10 @@ module Timed
114
209
  # ends
115
210
  elsif item_l.end <= item_t.end
116
211
  yield item_t.begin, item_l.end
117
- count += 1
118
212
 
119
213
  # 3: The leading item ends after the trailing one
120
214
  else
121
215
  yield item_t.begin, item_t.end
122
- count += 1
123
216
 
124
217
  # Swap leading and trailing
125
218
  item_l, item_t = item_t, item_l
@@ -131,8 +224,6 @@ module Timed
131
224
  # Swap leading and trailing if needed
132
225
  item_l, item_t = item_t, item_l if item_l.begin > item_t.begin
133
226
  end
134
-
135
- count
136
227
  end
137
228
 
138
229
  # Returns a new sequence with items that make up the intersection between
@@ -140,15 +231,75 @@ module Timed
140
231
 
141
232
  def intersect(other)
142
233
  intersections(other)
143
- .with_object(self.class.new) { |(b, e), a| a << Item.new(b, e) }
234
+ .with_object(self.class.new) { |(b, e), a| a << create_item(b, e) }
144
235
  end
145
236
 
146
237
  alias & intersect
147
238
 
148
239
  # More efficient than first calling #intersect and then #time on the result.
240
+ #
241
+ # from - a point in time to start from.
242
+ # to - a point in time to stop at.
243
+ #
244
+ # Returns the total time of the intersection between this sequence and the
245
+ # other one.
246
+
247
+ def intersect_time(other, from: nil, to: nil)
248
+ enum = intersections(other)
249
+ total = 0
250
+
251
+ if from
252
+ # Reuse the variable total. It's perhaps a bit messy
253
+ # and confusing but it works.
254
+ _, total = enum.next until total > from
255
+ total -= from
256
+ end
257
+
258
+ if to
259
+ loop do
260
+ b, e = enum.next
261
+
262
+ if e > to
263
+ total += to - b unless b >= to
264
+ break
265
+ end
266
+
267
+ total += e - b
268
+ end
269
+ else
270
+ loop do
271
+ b, e = enum.next
272
+ total += e - b
273
+ end
274
+ end
275
+
276
+ total
277
+ rescue StopIteration
278
+ return 0
279
+ end
280
+
281
+ # Protected factory method for creating items compatible with the sequence.
282
+ # This method is called whenever an arbitrary object is pushed or unshifted
283
+ # onto the list and need to be wraped inside an Item.
284
+ #
285
+ # args - any arguments will be passed on to Item.new.
286
+ #
287
+ # Returns a new Item.
288
+
289
+ protected def create_item(*args)
290
+ Item.new(*args)
291
+ end
292
+
293
+ # Private helper method for (re)defining method on the singleton class.
294
+ #
295
+ # name - symbol name of the method.
296
+ # body - proc that will be used as method body.
149
297
 
150
- def intersect_time(other)
151
- intersections(other).reduce(0) { |a, (b, e)| a + e - b }
298
+ private def redefine_method name, body
299
+ singleton_class.send :remove_method, name
300
+ rescue NameError
301
+ ensure
302
+ singleton_class.send :define_method, name, body
152
303
  end
153
304
  end
154
305
  end
data/lib/timed/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Timed
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
data/timed.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "linked", "~> 0.1.3"
22
+ spec.add_dependency "linked", "~> 0.3"
23
23
 
24
24
  spec.add_development_dependency "bundler", "~> 1.12"
25
25
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timed
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Lindberg
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-08-08 00:00:00.000000000 Z
11
+ date: 2016-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: linked
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.1.3
19
+ version: '0.3'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.1.3
26
+ version: '0.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement