hiccup 0.4.0 → 0.4.1
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.
- 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
|