hiccup 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e36feec08a5a3bec9f239244d7da51952930cae4
4
+ data.tar.gz: f9a5e21e9a5e4ef0f38b0ce49f138ad8bea0963d
5
+ SHA512:
6
+ metadata.gz: 068192d3b01e740acb6b356a9e4dadeb980c6f1c22bc68406d3480441f54f8a0239d942fa617c8e7e02032be81b74d921f1c3c0b99181687f6f9be310ee414df
7
+ data.tar.gz: 4433629beecf17aa82e1355413bce1546c2d4fcb0febd481e8689337131492fc702d0bd2b017b32fff744308a87c1e3c571d83402463875eaf7c3d4e887efd46
File without changes
File without changes
@@ -84,5 +84,26 @@ module Hiccup
84
84
 
85
85
 
86
86
 
87
+ def first_n_occurrences(limit)
88
+ n_occurrences_on_or_after(limit, start_date)
89
+ end
90
+
91
+ def n_occurrences_after(limit, date)
92
+ n_occurrences_on_or_after(limit, date.to_date + 1)
93
+ end
94
+
95
+ def n_occurrences_on_or_after(limit, date)
96
+ return [] if ends? and date > end_date
97
+
98
+ occurrences = []
99
+ enum = enumerator.new(self, date)
100
+ while (occurrence = enum.next) && occurrences.length < limit
101
+ occurrences << occurrence
102
+ end
103
+ occurrences
104
+ end
105
+
106
+
107
+
87
108
  end
88
109
  end
@@ -3,6 +3,9 @@ require 'active_support/core_ext/date/conversions'
3
3
  require 'hiccup/core_ext/enumerable'
4
4
  require 'hiccup/core_ext/hash'
5
5
  require "hiccup/core_ext/date"
6
+ require 'hiccup/inferable/dates_enumerator'
7
+ require 'hiccup/inferable/guesser'
8
+ require 'hiccup/inferable/score'
6
9
 
7
10
 
8
11
  module Hiccup
@@ -17,7 +20,7 @@ module Hiccup
17
20
 
18
21
  dates = extract_array_of_dates!(dates)
19
22
  enumerator = DatesEnumerator.new(dates)
20
- guesser = Guesser.new(self, {verbose: verbosity >= 2})
23
+ guesser = options.fetch :guesser, Guesser.new(self, options.merge(verbose: verbosity >= 2))
21
24
  schedules = []
22
25
 
23
26
  confidences = []
@@ -116,288 +119,5 @@ module Hiccup
116
119
 
117
120
 
118
121
  end
119
-
120
-
121
-
122
- class Guesser
123
-
124
- def initialize(klass, options={})
125
- @klass = klass
126
- @verbose = options.fetch(:verbose, false)
127
- start!
128
- end
129
-
130
- attr_reader :confidence, :schedule, :dates
131
-
132
- def start!
133
- @dates = []
134
- @schedule = nil
135
- @confidence = 0
136
- end
137
- alias :restart! :start!
138
-
139
-
140
-
141
-
142
- def <<(date)
143
- @dates << date
144
- @schedule, @confidence = best_schedule_for(@dates)
145
- date
146
- end
147
-
148
- def count
149
- @dates.length
150
- end
151
-
152
- def predicted?(date)
153
- @schedule && @schedule.contains?(date)
154
- end
155
-
156
-
157
-
158
- def best_schedule_for(dates)
159
- guesses = generate_guesses(dates)
160
- pick_best_guess(guesses, dates)
161
- end
162
-
163
- def generate_guesses(dates)
164
- @start_date = dates.first
165
- @end_date = dates.last
166
- [].tap do |guesses|
167
- guesses.concat generate_yearly_guesses(dates)
168
- guesses.concat generate_monthly_guesses(dates)
169
- guesses.concat generate_weekly_guesses(dates)
170
- end
171
- end
172
-
173
- def generate_yearly_guesses(dates)
174
- histogram_of_patterns = dates.to_histogram do |date|
175
- [date.month, date.day]
176
- end
177
- patterns_by_popularity = histogram_of_patterns.flip # => {1 => [...], 2 => [...], 5 => [a, b]}
178
- highest_popularity = patterns_by_popularity.keys.max # => 5
179
- most_popular = patterns_by_popularity[highest_popularity].first # => a
180
- start_date = Date.new(@start_date.year, *most_popular)
181
-
182
- [].tap do |guesses|
183
- (1...5).each do |skip|
184
- guesses << @klass.new.tap do |schedule|
185
- schedule.kind = :annually
186
- schedule.start_date = start_date
187
- schedule.end_date = @end_date
188
- schedule.skip = skip
189
- end
190
- end
191
- end
192
- end
193
-
194
- def generate_monthly_guesses(dates)
195
- histogram_of_patterns = dates.to_histogram do |date|
196
- [date.get_nth_wday_of_month, Date::DAYNAMES[date.wday]]
197
- end
198
- patterns_by_popularity = histogram_of_patterns.flip
199
-
200
- histogram_of_days = dates.to_histogram(&:day)
201
- days_by_popularity = histogram_of_days.flip
202
-
203
- if @verbose
204
- puts "",
205
- " monthly analysis:",
206
- " input: #{dates.inspect}",
207
- " histogram (weekday): #{histogram_of_patterns.inspect}",
208
- " by_popularity (weekday): #{patterns_by_popularity.inspect}",
209
- " histogram (day): #{histogram_of_days.inspect}",
210
- " by_popularity (day): #{days_by_popularity.inspect}"
211
- end
212
-
213
- [].tap do |guesses|
214
- (1...5).each do |skip|
215
- enumerate_by_popularity(days_by_popularity) do |days|
216
- guesses << @klass.new.tap do |schedule|
217
- schedule.kind = :monthly
218
- schedule.start_date = @start_date
219
- schedule.end_date = @end_date
220
- schedule.skip = skip
221
- schedule.monthly_pattern = days
222
- end
223
- end
224
-
225
- enumerate_by_popularity(patterns_by_popularity) do |patterns|
226
- guesses << @klass.new.tap do |schedule|
227
- schedule.kind = :monthly
228
- schedule.start_date = @start_date
229
- schedule.end_date = @end_date
230
- schedule.skip = skip
231
- schedule.monthly_pattern = patterns
232
- end
233
- end
234
- end
235
- end
236
- end
237
-
238
- def generate_weekly_guesses(dates)
239
- [].tap do |guesses|
240
- histogram_of_wdays = dates.to_histogram do |date|
241
- Date::DAYNAMES[date.wday]
242
- end
243
- wdays_by_popularity = histogram_of_wdays.flip
244
-
245
- if @verbose
246
- puts "",
247
- " weekly analysis:",
248
- " input: #{dates.inspect}",
249
- " histogram: #{histogram_of_wdays.inspect}",
250
- " by_popularity: #{wdays_by_popularity.inspect}"
251
- end
252
-
253
- (1...5).each do |skip|
254
- enumerate_by_popularity(wdays_by_popularity) do |wdays|
255
- guesses << @klass.new.tap do |schedule|
256
- schedule.kind = :weekly
257
- schedule.start_date = @start_date
258
- schedule.end_date = @end_date
259
- schedule.skip = skip
260
- schedule.weekly_pattern = wdays
261
- end
262
- end
263
- end
264
- end
265
- end
266
-
267
-
268
-
269
- # Expects a hash of values grouped by popularity
270
- # Yields the most popular values first, and then
271
- # increasingly less popular values
272
- def enumerate_by_popularity(values_by_popularity)
273
- popularities = values_by_popularity.keys.sort.reverse
274
- popularities.length.times do |i|
275
- at_popularities = popularities.take(i + 1)
276
- yield values_by_popularity.values_at(*at_popularities).flatten(1)
277
- end
278
- end
279
-
280
-
281
-
282
- def pick_best_guess(guesses, dates)
283
- scored_guesses = guesses \
284
- .map { |guess| [guess, score_guess(guess, dates)] } \
285
- .sort_by { |(guess, score)| -score.to_f }
286
-
287
- if @verbose
288
- puts "\nGUESSES FOR #{dates}:"
289
- scored_guesses.each do |(guess, score)|
290
- puts " (%.3f p/%.3f b/%.3f c/%.3f) #{guess.humanize}" % [
291
- score.to_f,
292
- score.prediction_rate,
293
- score.brick_penalty,
294
- score.complexity_penalty]
295
- end
296
- puts ""
297
- end
298
-
299
- scored_guesses.first
300
- end
301
-
302
- def score_guess(guess, input_dates)
303
- predicted_dates = guess.occurrences_between(guess.start_date, guess.end_date)
304
-
305
- # prediction_rate is the percent of input dates predicted
306
- predictions = (predicted_dates & input_dates).length
307
- prediction_rate = Float(predictions) / Float(input_dates.length)
308
-
309
- # bricks are dates predicted by this guess but not in the input
310
- bricks = (predicted_dates - input_dates).length
311
-
312
- # brick_rate is the percent of bricks to predictions
313
- # A brick_rate >= 1 means that this guess bricks more than it predicts
314
- brick_rate = Float(bricks) / Float(input_dates.length)
315
-
316
- # complexity measures how many rules are necesary
317
- # to describe the pattern
318
- complexity = complexity_of(guess)
319
-
320
- # complexity_rate is the number of rules per inputs
321
- complexity_rate = Float(complexity) / Float(input_dates.length)
322
-
323
- Score.new(prediction_rate, brick_rate, complexity_rate)
324
- end
325
-
326
- def complexity_of(schedule)
327
- return schedule.weekly_pattern.length if schedule.weekly?
328
- return schedule.monthly_pattern.length if schedule.monthly?
329
- 1
330
- end
331
-
332
-
333
-
334
- class Score < Struct.new(:prediction_rate, :brick_rate, :complexity_rate)
335
-
336
- # as brick rate rises, our confidence in this guess drops
337
- def brick_penalty
338
- brick_penalty = brick_rate * 0.33
339
- brick_penalty = 1 if brick_penalty > 1
340
- brick_penalty
341
- end
342
-
343
- # as the complexity rises, our confidence in this guess drops
344
- # this hash table is a stand-in for a proper formala
345
- #
346
- # A complexity of 1 means that 1 rule is required per input
347
- # date. This means we haven't really discovered a pattern.
348
- def complexity_penalty
349
- complexity_rate
350
- end
351
-
352
- # our confidence is weakened by bricks and complexity
353
- def confidence
354
- confidence = 1.0
355
- confidence *= (1 - brick_penalty)
356
- confidence *= (1 - complexity_penalty)
357
- confidence
358
- end
359
-
360
- # a number between 0 and 1
361
- def to_f
362
- prediction_rate * confidence
363
- end
364
-
365
- end
366
-
367
-
368
-
369
- end
370
-
371
-
372
-
373
- class DatesEnumerator
374
-
375
- def initialize(dates)
376
- @dates = dates
377
- @last_index = @dates.length - 1
378
- @index = -1
379
- end
380
-
381
- attr_reader :index
382
-
383
- def done?
384
- @index == @last_index
385
- end
386
-
387
- def next
388
- @index += 1
389
- raise OutOfRangeException if @index > @last_index
390
- @dates[@index]
391
- end
392
-
393
- def rewind_by(n)
394
- @index -= n
395
- @index = -1 if @index < -1
396
- end
397
-
398
- end
399
-
400
-
401
-
402
122
  end
403
123
  end
@@ -0,0 +1,30 @@
1
+ module Hiccup
2
+ module Inferable
3
+ class DatesEnumerator
4
+
5
+ def initialize(dates)
6
+ @dates = dates
7
+ @last_index = @dates.length - 1
8
+ @index = -1
9
+ end
10
+
11
+ attr_reader :index
12
+
13
+ def done?
14
+ @index == @last_index
15
+ end
16
+
17
+ def next
18
+ @index += 1
19
+ raise OutOfRangeException if @index > @last_index
20
+ @dates[@index]
21
+ end
22
+
23
+ def rewind_by(n)
24
+ @index -= n
25
+ @index = -1 if @index < -1
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,180 @@
1
+ require 'hiccup/inferable/scorer'
2
+
3
+ module Hiccup
4
+ module Inferable
5
+ class Guesser
6
+
7
+ def initialize(klass, options={})
8
+ @klass = klass
9
+ @verbose = options.fetch(:verbose, false)
10
+ @allow_skips = options.fetch(:allow_skips, true)
11
+ @max_complexity = options.fetch(:max_complexity, 3)
12
+ @scorer = options.fetch(:scorer, Scorer.new(options))
13
+ start!
14
+ end
15
+
16
+ attr_reader :confidence, :schedule, :dates, :scorer, :max_complexity
17
+
18
+ def allow_skips?
19
+ @allow_skips
20
+ end
21
+
22
+ def start!
23
+ @dates = []
24
+ @schedule = nil
25
+ @confidence = 0
26
+ end
27
+ alias :restart! :start!
28
+
29
+
30
+
31
+
32
+ def <<(date)
33
+ @dates << date
34
+ @schedule, @confidence = best_schedule_for(@dates)
35
+ date
36
+ end
37
+
38
+ def count
39
+ @dates.length
40
+ end
41
+
42
+ def predicted?(date)
43
+ @schedule && @schedule.contains?(date)
44
+ end
45
+
46
+
47
+
48
+ def best_schedule_for(dates)
49
+ guesses = generate_guesses(dates)
50
+ scorer.pick_best_guess(guesses, dates)
51
+ end
52
+
53
+ def generate_guesses(dates)
54
+ @start_date = dates.first
55
+ @end_date = dates.last
56
+ [].tap do |guesses|
57
+ guesses.concat generate_yearly_guesses(dates)
58
+ guesses.concat generate_monthly_guesses(dates)
59
+ guesses.concat generate_weekly_guesses(dates)
60
+ end
61
+ end
62
+
63
+ def generate_yearly_guesses(dates)
64
+ histogram_of_patterns = dates.to_histogram do |date|
65
+ [date.month, date.day]
66
+ end
67
+ patterns_by_popularity = histogram_of_patterns.flip # => {1 => [...], 2 => [...], 5 => [a, b]}
68
+ highest_popularity = patterns_by_popularity.keys.max # => 5
69
+ most_popular = patterns_by_popularity[highest_popularity].first # => a
70
+ start_date = Date.new(@start_date.year, *most_popular)
71
+
72
+ [].tap do |guesses|
73
+ skip_range.each do |skip|
74
+ guesses << @klass.new.tap do |schedule|
75
+ schedule.kind = :annually
76
+ schedule.start_date = start_date
77
+ schedule.end_date = @end_date
78
+ schedule.skip = skip
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def generate_monthly_guesses(dates)
85
+ histogram_of_patterns = dates.to_histogram do |date|
86
+ [date.get_nth_wday_of_month, Date::DAYNAMES[date.wday]]
87
+ end
88
+ patterns_by_popularity = histogram_of_patterns.flip
89
+
90
+ histogram_of_days = dates.to_histogram(&:day)
91
+ days_by_popularity = histogram_of_days.flip
92
+
93
+ if @verbose
94
+ puts "",
95
+ " monthly analysis:",
96
+ " input: #{dates.inspect}",
97
+ " histogram (weekday): #{histogram_of_patterns.inspect}",
98
+ " by_popularity (weekday): #{patterns_by_popularity.inspect}",
99
+ " histogram (day): #{histogram_of_days.inspect}",
100
+ " by_popularity (day): #{days_by_popularity.inspect}"
101
+ end
102
+
103
+ [].tap do |guesses|
104
+ skip_range.each do |skip|
105
+ enumerate_by_popularity(days_by_popularity) do |days|
106
+ next if days.length > max_complexity
107
+ guesses << @klass.new.tap do |schedule|
108
+ schedule.kind = :monthly
109
+ schedule.start_date = @start_date
110
+ schedule.end_date = @end_date
111
+ schedule.skip = skip
112
+ schedule.monthly_pattern = days
113
+ end
114
+ end
115
+
116
+ enumerate_by_popularity(patterns_by_popularity) do |patterns|
117
+ next if patterns.length > max_complexity
118
+ guesses << @klass.new.tap do |schedule|
119
+ schedule.kind = :monthly
120
+ schedule.start_date = @start_date
121
+ schedule.end_date = @end_date
122
+ schedule.skip = skip
123
+ schedule.monthly_pattern = patterns
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def generate_weekly_guesses(dates)
131
+ [].tap do |guesses|
132
+ histogram_of_wdays = dates.to_histogram do |date|
133
+ Date::DAYNAMES[date.wday]
134
+ end
135
+ wdays_by_popularity = histogram_of_wdays.flip
136
+
137
+ if @verbose
138
+ puts "",
139
+ " weekly analysis:",
140
+ " input: #{dates.inspect}",
141
+ " histogram: #{histogram_of_wdays.inspect}",
142
+ " by_popularity: #{wdays_by_popularity.inspect}"
143
+ end
144
+
145
+ skip_range.each do |skip|
146
+ enumerate_by_popularity(wdays_by_popularity) do |wdays|
147
+ next if wdays.length > max_complexity
148
+ guesses << @klass.new.tap do |schedule|
149
+ schedule.kind = :weekly
150
+ schedule.start_date = @start_date
151
+ schedule.end_date = @end_date
152
+ schedule.skip = skip
153
+ schedule.weekly_pattern = wdays
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ def skip_range
161
+ return 1..1 unless allow_skips?
162
+ 1...5
163
+ end
164
+
165
+
166
+
167
+ # Expects a hash of values grouped by popularity
168
+ # Yields the most popular values first, and then
169
+ # increasingly less popular values
170
+ def enumerate_by_popularity(values_by_popularity)
171
+ popularities = values_by_popularity.keys.sort.reverse
172
+ popularities.length.times do |i|
173
+ at_popularities = popularities.take(i + 1)
174
+ yield values_by_popularity.values_at(*at_popularities).flatten(1)
175
+ end
176
+ end
177
+
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,38 @@
1
+ module Hiccup
2
+ module Inferable
3
+
4
+ class Score < Struct.new(:prediction_rate, :brick_rate, :complexity_rate)
5
+
6
+ # as brick rate rises, our confidence in this guess drops
7
+ def brick_penalty
8
+ brick_penalty = brick_rate * 0.33
9
+ brick_penalty = 1 if brick_penalty > 1
10
+ brick_penalty
11
+ end
12
+
13
+ # as the complexity rises, our confidence in this guess drops
14
+ # this hash table is a stand-in for a proper formala
15
+ #
16
+ # A complexity of 1 means that 1 rule is required per input
17
+ # date. This means we haven't really discovered a pattern.
18
+ def complexity_penalty
19
+ complexity_rate
20
+ end
21
+
22
+ # our confidence is weakened by bricks and complexity
23
+ def confidence
24
+ confidence = 1.0
25
+ confidence *= (1 - brick_penalty)
26
+ confidence *= (1 - complexity_penalty)
27
+ confidence
28
+ end
29
+
30
+ # a number between 0 and 1
31
+ def to_f
32
+ prediction_rate * confidence
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,67 @@
1
+ module Hiccup
2
+ module Inferable
3
+ class Scorer
4
+
5
+ def initialize(options={})
6
+ @verbose = options.fetch(:verbose, false)
7
+ end
8
+
9
+
10
+
11
+ def pick_best_guess(guesses, dates)
12
+ scored_guesses = guesses \
13
+ .map { |guess| [guess, score_guess(guess, dates)] } \
14
+ .sort_by { |(guess, score)| -score.to_f }
15
+
16
+ if @verbose
17
+ puts "\nGUESSES FOR #{dates}:"
18
+ scored_guesses.each do |(guess, score)|
19
+ puts " (%.3f p/%.3f b/%.3f c/%.3f) #{guess.humanize}" % [
20
+ score.to_f,
21
+ score.prediction_rate,
22
+ score.brick_penalty,
23
+ score.complexity_penalty]
24
+ end
25
+ puts ""
26
+ end
27
+
28
+ scored_guesses.first
29
+ end
30
+
31
+
32
+
33
+ def score_guess(guess, input_dates)
34
+ predicted_dates = guess.occurrences_between(guess.start_date, guess.end_date)
35
+
36
+ # prediction_rate is the percent of input dates predicted
37
+ predictions = (predicted_dates & input_dates).length
38
+ prediction_rate = Float(predictions) / Float(input_dates.length)
39
+
40
+ # bricks are dates predicted by this guess but not in the input
41
+ bricks = (predicted_dates - input_dates).length
42
+
43
+ # brick_rate is the percent of bricks to predictions
44
+ # A brick_rate >= 1 means that this guess bricks more than it predicts
45
+ brick_rate = Float(bricks) / Float(input_dates.length)
46
+
47
+ # complexity measures how many rules are necesary
48
+ # to describe the pattern
49
+ complexity = complexity_of(guess)
50
+
51
+ # complexity_rate is the number of rules per inputs
52
+ complexity_rate = Float(complexity) / Float(input_dates.length)
53
+
54
+ Score.new(prediction_rate, brick_rate, complexity_rate)
55
+ end
56
+
57
+
58
+
59
+ def complexity_of(schedule)
60
+ return schedule.weekly_pattern.length if schedule.weekly?
61
+ return schedule.monthly_pattern.length if schedule.monthly?
62
+ 1
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -42,4 +42,4 @@ module Hiccup
42
42
 
43
43
 
44
44
  end
45
- end
45
+ end
@@ -1,3 +1,3 @@
1
1
  module Hiccup
2
- VERSION = "0.4.3"
2
+ VERSION = "0.4.4"
3
3
  end
@@ -3,7 +3,6 @@ require "test_helper"
3
3
 
4
4
  class EnumerableTest < ActiveSupport::TestCase
5
5
  include Hiccup
6
- PERFORMANCE_TEST = false
7
6
 
8
7
 
9
8
 
@@ -347,7 +346,7 @@ class EnumerableTest < ActiveSupport::TestCase
347
346
 
348
347
 
349
348
 
350
- if PERFORMANCE_TEST
349
+ if ENV['PERFORMANCE_TEST']
351
350
  test "performance test" do
352
351
  n = 100
353
352
 
@@ -248,9 +248,15 @@ class InferableTest < ActiveSupport::TestCase
248
248
 
249
249
 
250
250
 
251
- test "should diabolically complex schedules" do
251
+ test "should reject diabolically complex schedules by default" do
252
252
  dates = %w{2012-11-06 2012-11-08 2012-11-15 2012-11-20 2012-11-27 2012-11-29 2013-02-05 2013-02-14 2013-02-21 2013-02-19 2013-02-26 2013-05-07 2013-05-09 2013-05-16 2013-05-28 2013-05-21 2013-05-30}
253
253
  schedules = Schedule.infer(dates)
254
+ refute_equal ["The first Tuesday, second Thursday, third Thursday, third Tuesday, fourth Tuesday, and fifth Thursday of every third month"], schedules.map(&:humanize)
255
+ end
256
+
257
+ test "should infer diabolically complex schedules on demand" do
258
+ dates = %w{2012-11-06 2012-11-08 2012-11-15 2012-11-20 2012-11-27 2012-11-29 2013-02-05 2013-02-14 2013-02-21 2013-02-19 2013-02-26 2013-05-07 2013-05-09 2013-05-16 2013-05-28 2013-05-21 2013-05-30}
259
+ schedules = Schedule.infer(dates, max_complexity: 99)
254
260
  assert_equal ["The first Tuesday, second Thursday, third Thursday, third Tuesday, fourth Tuesday, and fifth Thursday of every third month"], schedules.map(&:humanize)
255
261
  end
256
262
 
@@ -0,0 +1,35 @@
1
+ require "test_helper"
2
+ require "benchmark"
3
+
4
+ class PerformanceTest < ActiveSupport::TestCase
5
+ include Hiccup
6
+
7
+
8
+ { 100 => 50,
9
+ 500 => 50,
10
+ 1000 => 50 }.each do |number, expected_duration|
11
+ test "should generated guesses from #{number} dates in under #{expected_duration}ms" do
12
+ guesser = Hiccup::Inferable::Guesser.new(Hiccup::Schedule)
13
+ dates = (0...number).map { |i| Date.new(2010, 1, 1) + i.week }
14
+ duration = Benchmark.ms { guesser.generate_guesses(dates) }
15
+ # puts "\e[33m\e[1m#{number}\e[0m\e[33m dates took \e[1m%.2fms\e[0m" % duration
16
+ assert duration <= expected_duration, "It took %.2fms" % duration
17
+ end
18
+ end
19
+
20
+
21
+ # Inferring 500 dates still takes 10 seconds.
22
+ # It spends 7.3 of those seconds predicting dates,
23
+ # 6.9 of those predicting monthly or weekly dates.
24
+ { 10 => 0.1.seconds,
25
+ 50 => 0.5.seconds,
26
+ 100 => 1.0.seconds }.each do |number, expected_duration|
27
+ test "should infer a schedule from #{number} dates in under #{expected_duration} second(s)" do
28
+ dates = (0...number).map { |i| Date.new(2010, 1, 1) + i.week }
29
+ duration = Benchmark.ms { Schedule.infer(dates, verbosity: 0) } / 1000
30
+ assert duration <= expected_duration, "It took %.2f seconds" % duration
31
+ end
32
+ end
33
+
34
+
35
+ end
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hiccup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
5
- prerelease:
4
+ version: 0.4.4
6
5
  platform: ruby
7
6
  authors:
8
7
  - Bob Lail
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-01-03 00:00:00.000000000 Z
11
+ date: 2013-09-18 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: activesupport
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
@@ -22,7 +20,6 @@ dependencies:
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ~>
28
25
  - !ruby/object:Gem::Version
@@ -30,39 +27,34 @@ dependencies:
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: builder
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - '>='
36
32
  - !ruby/object:Gem::Version
37
33
  version: '0'
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - '>='
44
39
  - !ruby/object:Gem::Version
45
40
  version: '0'
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: ri_cal
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ! '>='
45
+ - - '>='
52
46
  - !ruby/object:Gem::Version
53
47
  version: '0'
54
48
  type: :development
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ! '>='
52
+ - - '>='
60
53
  - !ruby/object:Gem::Version
61
54
  version: '0'
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: rails
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
59
  - - ~>
68
60
  - !ruby/object:Gem::Version
@@ -70,7 +62,6 @@ dependencies:
70
62
  type: :development
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
66
  - - ~>
76
67
  - !ruby/object:Gem::Version
@@ -78,49 +69,43 @@ dependencies:
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: turn
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
- - - ! '>='
73
+ - - '>='
84
74
  - !ruby/object:Gem::Version
85
75
  version: '0'
86
76
  type: :development
87
77
  prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
- - - ! '>='
80
+ - - '>='
92
81
  - !ruby/object:Gem::Version
93
82
  version: '0'
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: simplecov
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
- - - ! '>='
87
+ - - '>='
100
88
  - !ruby/object:Gem::Version
101
89
  version: '0'
102
90
  type: :development
103
91
  prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
93
  requirements:
107
- - - ! '>='
94
+ - - '>='
108
95
  - !ruby/object:Gem::Version
109
96
  version: '0'
110
97
  - !ruby/object:Gem::Dependency
111
98
  name: pry
112
99
  requirement: !ruby/object:Gem::Requirement
113
- none: false
114
100
  requirements:
115
- - - ! '>='
101
+ - - '>='
116
102
  - !ruby/object:Gem::Version
117
103
  version: '0'
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
107
  requirements:
123
- - - ! '>='
108
+ - - '>='
124
109
  - !ruby/object:Gem::Version
125
110
  version: '0'
126
111
  description: Hiccup mixes a-la-cart recurrence features into your data structure.
@@ -152,6 +137,10 @@ files:
152
137
  - lib/hiccup/enumerable/weekly_enumerator.rb
153
138
  - lib/hiccup/humanizable.rb
154
139
  - lib/hiccup/inferable.rb
140
+ - lib/hiccup/inferable/dates_enumerator.rb
141
+ - lib/hiccup/inferable/guesser.rb
142
+ - lib/hiccup/inferable/score.rb
143
+ - lib/hiccup/inferable/scorer.rb
155
144
  - lib/hiccup/schedule.rb
156
145
  - lib/hiccup/serializable/ical.rb
157
146
  - lib/hiccup/serializers/ical.rb
@@ -163,37 +152,31 @@ files:
163
152
  - test/humanizable_test.rb
164
153
  - test/ical_serializable_test.rb
165
154
  - test/inferrable_test.rb
155
+ - test/performance_test.rb
166
156
  - test/test_helper.rb
167
157
  - test/validatable_test.rb
168
158
  homepage: http://boblail.github.com/hiccup/
169
159
  licenses: []
160
+ metadata: {}
170
161
  post_install_message:
171
162
  rdoc_options: []
172
163
  require_paths:
173
164
  - lib
174
165
  required_ruby_version: !ruby/object:Gem::Requirement
175
- none: false
176
166
  requirements:
177
- - - ! '>='
167
+ - - '>='
178
168
  - !ruby/object:Gem::Version
179
169
  version: '0'
180
- segments:
181
- - 0
182
- hash: -936135857702157052
183
170
  required_rubygems_version: !ruby/object:Gem::Requirement
184
- none: false
185
171
  requirements:
186
- - - ! '>='
172
+ - - '>='
187
173
  - !ruby/object:Gem::Version
188
174
  version: '0'
189
- segments:
190
- - 0
191
- hash: -936135857702157052
192
175
  requirements: []
193
176
  rubyforge_project: hiccup
194
- rubygems_version: 1.8.24
177
+ rubygems_version: 2.0.2
195
178
  signing_key:
196
- specification_version: 3
179
+ specification_version: 4
197
180
  summary: A library for working with things that recur
198
181
  test_files:
199
182
  - test/core_ext_date_test.rb
@@ -202,5 +185,6 @@ test_files:
202
185
  - test/humanizable_test.rb
203
186
  - test/ical_serializable_test.rb
204
187
  - test/inferrable_test.rb
188
+ - test/performance_test.rb
205
189
  - test/test_helper.rb
206
190
  - test/validatable_test.rb