hiccup 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/hiccup/enumerable.rb +10 -17
- data/lib/hiccup/enumerable/schedule_enumerator.rb +2 -0
- data/lib/hiccup/enumerable/weekly_enumerator.rb +1 -1
- data/lib/hiccup/humanizable.rb +1 -1
- data/lib/hiccup/inferable.rb +70 -39
- data/lib/hiccup/version.rb +1 -1
- data/test/enumerable_test.rb +37 -0
- data/test/inferrable_test.rb +9 -2
- metadata +4 -4
data/lib/hiccup/enumerable.rb
CHANGED
@@ -11,17 +11,13 @@ module Hiccup
|
|
11
11
|
|
12
12
|
|
13
13
|
def enumerator
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
def kind=(value)
|
18
|
-
super
|
19
|
-
@enumerator = nil
|
14
|
+
"Hiccup::Enumerable::#{kind.to_s.classify}Enumerator".constantize
|
20
15
|
end
|
21
16
|
|
22
17
|
|
23
18
|
|
24
19
|
def occurrences_during_month(year, month)
|
20
|
+
puts "DEPRECATED: `occurrences_during_month` will be removed in 0.5.0. Use `occurrences_between` instead"
|
25
21
|
date1 = Date.new(year, month, 1)
|
26
22
|
date2 = Date.new(year, month, -1)
|
27
23
|
occurrences_between(date1, date2)
|
@@ -30,8 +26,6 @@ module Hiccup
|
|
30
26
|
|
31
27
|
|
32
28
|
def occurrences_between(earlier_date, later_date)
|
33
|
-
earlier_date = start_date if earlier_date < start_date
|
34
|
-
|
35
29
|
occurrences = []
|
36
30
|
enum = enumerator.new(self, earlier_date)
|
37
31
|
while (occurrence = enum.next) && (occurrence <= later_date)
|
@@ -43,42 +37,41 @@ module Hiccup
|
|
43
37
|
|
44
38
|
|
45
39
|
def first_occurrence_on_or_after(date)
|
46
|
-
|
40
|
+
return nil if ends? && date > end_date
|
47
41
|
enumerator.new(self, date).next
|
48
42
|
end
|
49
43
|
|
50
|
-
def
|
51
|
-
first_occurrence_on_or_after(date + 1)
|
44
|
+
def first_occurrence_after(date)
|
45
|
+
first_occurrence_on_or_after(date.to_date + 1)
|
52
46
|
end
|
47
|
+
alias :next_occurrence_after :first_occurrence_after
|
53
48
|
|
54
49
|
|
55
50
|
|
56
51
|
def first_occurrence_on_or_before(date)
|
57
|
-
|
52
|
+
return nil if date < start_date
|
58
53
|
enumerator.new(self, date).prev
|
59
54
|
end
|
60
55
|
|
61
56
|
def first_occurrence_before(date)
|
62
|
-
first_occurrence_on_or_before(date - 1)
|
57
|
+
first_occurrence_on_or_before(date.to_date - 1)
|
63
58
|
end
|
64
59
|
|
65
60
|
|
66
61
|
|
67
62
|
def occurs_on(date)
|
68
63
|
date = date.to_date
|
69
|
-
first_occurrence_on_or_after(date)
|
64
|
+
date == first_occurrence_on_or_after(date)
|
70
65
|
end
|
71
66
|
alias :contains? :occurs_on
|
72
67
|
|
73
68
|
|
74
69
|
|
75
70
|
def n_occurrences_before(limit, date)
|
76
|
-
n_occurrences_on_or_before(limit, date - 1)
|
71
|
+
n_occurrences_on_or_before(limit, date.to_date - 1)
|
77
72
|
end
|
78
73
|
|
79
74
|
def n_occurrences_on_or_before(limit, date)
|
80
|
-
date = end_date if (ends? && date > end_date)
|
81
|
-
|
82
75
|
occurrences = []
|
83
76
|
enum = enumerator.new(self, date)
|
84
77
|
while (occurrence = enum.prev) && occurrences.length < limit
|
data/lib/hiccup/humanizable.rb
CHANGED
data/lib/hiccup/inferable.rb
CHANGED
@@ -12,48 +12,89 @@ module Hiccup
|
|
12
12
|
module ClassMethods
|
13
13
|
|
14
14
|
def infer(dates, options={})
|
15
|
-
|
15
|
+
allow_null_schedules = options.fetch(:allow_null_schedules, false)
|
16
|
+
verbosity = options.fetch(:verbosity, (options[:verbose] ? 1 : 0)) # 0, 1, or 2
|
16
17
|
|
18
|
+
dates = extract_array_of_dates!(dates)
|
17
19
|
enumerator = DatesEnumerator.new(dates)
|
18
|
-
guesser = Guesser.new(self,
|
20
|
+
guesser = Guesser.new(self, {verbose: verbosity >= 2})
|
21
|
+
schedules = []
|
22
|
+
|
19
23
|
confidences = []
|
20
|
-
|
21
|
-
|
24
|
+
high_confidence_threshold = 0.6
|
25
|
+
min_confidence_threshold = 0.35
|
26
|
+
|
27
|
+
last_confident_schedule = nil
|
28
|
+
iterations_since_last_confident_schedule = 0
|
22
29
|
|
23
30
|
until enumerator.done?
|
24
31
|
date = enumerator.next
|
25
32
|
guesser << date
|
26
|
-
|
33
|
+
confidence = guesser.confidence.to_f
|
34
|
+
confidences << confidence
|
35
|
+
predicted = guesser.predicted?(date)
|
36
|
+
|
37
|
+
# if the last two confidences are both below a certain
|
38
|
+
# threshhold and both declining, back up to where we
|
39
|
+
# started to go wrong and start a new schedule.
|
40
|
+
|
41
|
+
confident = !(confidences.length >= 3 && (
|
42
|
+
(confidences[-1] < high_confidence_threshold &&
|
43
|
+
confidences[-2] < high_confidence_threshold &&
|
44
|
+
confidences[-1] < confidences[-2] &&
|
45
|
+
confidences[-2] < confidences[-3]) ||
|
46
|
+
(confidences[-1] < min_confidence_threshold &&
|
47
|
+
confidences[-2] < min_confidence_threshold)))
|
27
48
|
|
28
|
-
if
|
29
|
-
|
49
|
+
if predicted && confidence >= min_confidence_threshold
|
50
|
+
iterations_since_last_confident_schedule = 0
|
51
|
+
last_confident_schedule = guesser.schedule
|
30
52
|
else
|
31
|
-
|
53
|
+
iterations_since_last_confident_schedule += 1
|
32
54
|
end
|
33
55
|
|
34
|
-
|
56
|
+
rewind_by = iterations_since_last_confident_schedule == guesser.count ? iterations_since_last_confident_schedule - 1 : iterations_since_last_confident_schedule
|
35
57
|
|
36
|
-
# if the last two confidences are both below a certain
|
37
|
-
# threshhold and both declining, back up to where we
|
38
|
-
# started to go wrong and start a new schedule.
|
39
58
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
59
|
+
|
60
|
+
if verbosity >= 1
|
61
|
+
output = " #{enumerator.index.to_s.rjust(3)} #{date}"
|
62
|
+
output << " #{"[#{guesser.count}]".rjust(5)} => "
|
63
|
+
output << "~#{(guesser.confidence.to_f * 100).to_i.to_s.rjust(2, "0")} @ "
|
64
|
+
output << guesser.schedule.humanize.ljust(130)
|
65
|
+
output << " :( move back #{rewind_by}" unless confident
|
66
|
+
puts output
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
unless confident
|
72
|
+
|
73
|
+
if last_confident_schedule
|
74
|
+
schedules << last_confident_schedule
|
75
|
+
elsif allow_null_schedules
|
76
|
+
guesser.dates.take(guesser.count - rewind_by).each do |date|
|
77
|
+
schedules << self.new(:kind => :never, :start_date => date)
|
78
|
+
end
|
79
|
+
end
|
45
80
|
|
46
|
-
rewind_by -= 1 if rewind_by == guesser.count
|
47
81
|
enumerator.rewind_by(rewind_by)
|
48
82
|
guesser.restart!
|
49
83
|
confidences = []
|
50
|
-
|
84
|
+
iterations_since_last_confident_schedule = 0
|
85
|
+
last_confident_schedule = nil
|
51
86
|
end
|
52
87
|
end
|
53
88
|
|
54
|
-
|
89
|
+
if last_confident_schedule
|
90
|
+
schedules << last_confident_schedule
|
91
|
+
elsif allow_null_schedules
|
92
|
+
guesser.dates.each do |date|
|
93
|
+
schedules << self.new(:kind => :never, :start_date => date)
|
94
|
+
end
|
95
|
+
end
|
55
96
|
|
56
|
-
|
97
|
+
schedules
|
57
98
|
end
|
58
99
|
|
59
100
|
|
@@ -83,37 +124,25 @@ module Hiccup
|
|
83
124
|
def initialize(klass, options={})
|
84
125
|
@klass = klass
|
85
126
|
@verbose = options.fetch(:verbose, false)
|
86
|
-
@guesses = []
|
87
127
|
start!
|
88
128
|
end
|
89
129
|
|
90
|
-
attr_reader :
|
130
|
+
attr_reader :confidence, :schedule, :dates
|
91
131
|
|
92
132
|
def start!
|
93
|
-
save_current_guess!
|
94
|
-
reset_growing_understanding!
|
95
|
-
end
|
96
|
-
alias :restart! :start!
|
97
|
-
|
98
|
-
def stop!
|
99
|
-
start!
|
100
|
-
end
|
101
|
-
|
102
|
-
def save_current_guess!
|
103
|
-
@guesses << @schedule if @schedule
|
104
|
-
end
|
105
|
-
|
106
|
-
def reset_growing_understanding!
|
107
133
|
@dates = []
|
108
134
|
@schedule = nil
|
109
135
|
@confidence = 0
|
110
136
|
end
|
137
|
+
alias :restart! :start!
|
138
|
+
|
111
139
|
|
112
140
|
|
113
141
|
|
114
142
|
def <<(date)
|
115
143
|
@dates << date
|
116
144
|
@schedule, @confidence = best_schedule_for(@dates)
|
145
|
+
date
|
117
146
|
end
|
118
147
|
|
119
148
|
def count
|
@@ -149,7 +178,7 @@ module Hiccup
|
|
149
178
|
highest_popularity = patterns_by_popularity.keys.max # => 5
|
150
179
|
most_popular = patterns_by_popularity[highest_popularity].first # => a
|
151
180
|
start_date = Date.new(@start_date.year, *most_popular)
|
152
|
-
|
181
|
+
|
153
182
|
[].tap do |guesses|
|
154
183
|
(1...5).each do |skip|
|
155
184
|
guesses << @klass.new.tap do |schedule|
|
@@ -267,7 +296,7 @@ module Hiccup
|
|
267
296
|
puts ""
|
268
297
|
end
|
269
298
|
|
270
|
-
scored_guesses.
|
299
|
+
scored_guesses.first
|
271
300
|
end
|
272
301
|
|
273
302
|
def score_guess(guess, input_dates)
|
@@ -349,6 +378,8 @@ module Hiccup
|
|
349
378
|
@index = -1
|
350
379
|
end
|
351
380
|
|
381
|
+
attr_reader :index
|
382
|
+
|
352
383
|
def done?
|
353
384
|
@index == @last_index
|
354
385
|
end
|
data/lib/hiccup/version.rb
CHANGED
data/test/enumerable_test.rb
CHANGED
@@ -189,6 +189,43 @@ class EnumerableTest < ActiveSupport::TestCase
|
|
189
189
|
|
190
190
|
|
191
191
|
|
192
|
+
test "should not predict dates before the beginning of a schedule" do
|
193
|
+
schedule = Schedule.new({
|
194
|
+
:kind => :weekly,
|
195
|
+
:weekly_pattern => %w{Monday},
|
196
|
+
:start_date => Date.new(2011, 1, 3),
|
197
|
+
:ends => true,
|
198
|
+
:end_date => Date.new(2011, 1, 31)})
|
199
|
+
assert_equal nil, schedule.first_occurrence_before(Date.new(2011,1,3))
|
200
|
+
assert_equal nil, schedule.first_occurrence_on_or_before(Date.new(2011,1,2))
|
201
|
+
end
|
202
|
+
|
203
|
+
test "should not predict dates after the end of a schedule" do
|
204
|
+
schedule = Schedule.new({
|
205
|
+
:kind => :weekly,
|
206
|
+
:weekly_pattern => %w{Monday},
|
207
|
+
:start_date => Date.new(2011, 1, 3),
|
208
|
+
:ends => true,
|
209
|
+
:end_date => Date.new(2011, 1, 31)})
|
210
|
+
assert_equal nil, schedule.first_occurrence_after(Date.new(2011,1,31))
|
211
|
+
assert_equal nil, schedule.first_occurrence_on_or_after(Date.new(2011,2, 1))
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
|
216
|
+
test "all methods should take any kind of date as an argument" do
|
217
|
+
schedule = Schedule.new({
|
218
|
+
:kind => :weekly,
|
219
|
+
:weekly_pattern => %w{Monday},
|
220
|
+
:start_date => Date.new(2011, 1, 1),
|
221
|
+
:ends => true,
|
222
|
+
:end_date => Date.new(2011, 1, 31)})
|
223
|
+
assert_equal Date.new(2011, 1, 17), schedule.first_occurrence_after(Time.new(2011, 1, 10))
|
224
|
+
assert_equal Date.new(2011, 1, 3), schedule.first_occurrence_before(Time.new(2011, 1, 10))
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
|
192
229
|
def test_weekly_recurrence_and_skip
|
193
230
|
schedule = Schedule.new({
|
194
231
|
:kind => :weekly,
|
data/test/inferrable_test.rb
CHANGED
@@ -230,13 +230,20 @@ class InferableTest < ActiveSupport::TestCase
|
|
230
230
|
end
|
231
231
|
end
|
232
232
|
|
233
|
+
test "should create one non-recurring schedule for each break if asked" do
|
234
|
+
dates = %w{2012-03-05 2012-11-28 2012-12-05 2012-12-12} # a random Monday, then three Wednesdays
|
235
|
+
schedules = Schedule.infer(dates, allow_null_schedules: true)
|
236
|
+
|
237
|
+
assert_equal ["2012-03-05", "Every Wednesday"], schedules.map(&:humanize)
|
238
|
+
end
|
239
|
+
|
233
240
|
|
234
241
|
|
235
242
|
test "should infer multiple schedules from mixed input" do
|
236
243
|
dates = %w{2012-11-05 2012-11-12 2012-11-19 2012-11-28 2012-12-05 2012-12-12} # three Mondays then three Wednesdays
|
237
244
|
schedules = Schedule.infer(dates)
|
238
|
-
|
239
|
-
|
245
|
+
|
246
|
+
assert_equal ["Every Monday", "Every Wednesday"], schedules.map(&:humanize)
|
240
247
|
end
|
241
248
|
|
242
249
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hiccup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -179,7 +179,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
179
179
|
version: '0'
|
180
180
|
segments:
|
181
181
|
- 0
|
182
|
-
hash: -
|
182
|
+
hash: -1495672392285233886
|
183
183
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
184
184
|
none: false
|
185
185
|
requirements:
|
@@ -188,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
188
188
|
version: '0'
|
189
189
|
segments:
|
190
190
|
- 0
|
191
|
-
hash: -
|
191
|
+
hash: -1495672392285233886
|
192
192
|
requirements: []
|
193
193
|
rubyforge_project: hiccup
|
194
194
|
rubygems_version: 1.8.24
|