timed 0.1.0 → 0.2.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: 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