Class: Hiccup::Inferable::Guesser

Inherits:
Object
  • Object
show all
Defined in:
lib/hiccup/inferable.rb

Defined Under Namespace

Classes: Score

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(klass, options = {}) ⇒ Guesser

Returns a new instance of Guesser.



124
125
126
127
128
# File 'lib/hiccup/inferable.rb', line 124

def initialize(klass, options={})
  @klass = klass
  @verbose = options.fetch(:verbose, false)
  start!
end

Instance Attribute Details

#confidenceObject (readonly)

Returns the value of attribute confidence.



130
131
132
# File 'lib/hiccup/inferable.rb', line 130

def confidence
  @confidence
end

#datesObject (readonly)

Returns the value of attribute dates.



130
131
132
# File 'lib/hiccup/inferable.rb', line 130

def dates
  @dates
end

#scheduleObject (readonly)

Returns the value of attribute schedule.



130
131
132
# File 'lib/hiccup/inferable.rb', line 130

def schedule
  @schedule
end

Instance Method Details

#<<(date) ⇒ Object



142
143
144
145
146
# File 'lib/hiccup/inferable.rb', line 142

def <<(date)
  @dates << date
  @schedule, @confidence = best_schedule_for(@dates)
  date
end

#best_schedule_for(dates) ⇒ Object



158
159
160
161
# File 'lib/hiccup/inferable.rb', line 158

def best_schedule_for(dates)
  guesses = generate_guesses(dates)
  pick_best_guess(guesses, dates)
end

#complexity_of(schedule) ⇒ Object



326
327
328
329
330
# File 'lib/hiccup/inferable.rb', line 326

def complexity_of(schedule)
  return schedule.weekly_pattern.length if schedule.weekly?
  return schedule.monthly_pattern.length if schedule.monthly?
  1
end

#countObject



148
149
150
# File 'lib/hiccup/inferable.rb', line 148

def count
  @dates.length
end

#enumerate_by_popularity(values_by_popularity) ⇒ Object

Expects a hash of values grouped by popularity Yields the most popular values first, and then increasingly less popular values



272
273
274
275
276
277
278
# File 'lib/hiccup/inferable.rb', line 272

def enumerate_by_popularity(values_by_popularity)
  popularities = values_by_popularity.keys.sort.reverse
  popularities.length.times do |i|
    at_popularities = popularities.take(i + 1)
    yield values_by_popularity.values_at(*at_popularities).flatten(1)
  end
end

#generate_guesses(dates) ⇒ Object



163
164
165
166
167
168
169
170
171
# File 'lib/hiccup/inferable.rb', line 163

def generate_guesses(dates)
  @start_date = dates.first
  @end_date = dates.last
  [].tap do |guesses|
    guesses.concat generate_yearly_guesses(dates)
    guesses.concat generate_monthly_guesses(dates)
    guesses.concat generate_weekly_guesses(dates)
  end
end

#generate_monthly_guesses(dates) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/hiccup/inferable.rb', line 194

def generate_monthly_guesses(dates)
  histogram_of_patterns = dates.to_histogram do |date|
    [date.get_nth_wday_of_month, Date::DAYNAMES[date.wday]]
  end
  patterns_by_popularity = histogram_of_patterns.flip
  
  histogram_of_days = dates.to_histogram(&:day)
  days_by_popularity = histogram_of_days.flip
  
  if @verbose
    puts "",
         "  monthly analysis:",
         "    input: #{dates.inspect}",
         "    histogram (weekday): #{histogram_of_patterns.inspect}",
         "    by_popularity (weekday): #{patterns_by_popularity.inspect}",
         "    histogram (day): #{histogram_of_days.inspect}",
         "    by_popularity (day): #{days_by_popularity.inspect}"
  end
  
  [].tap do |guesses|
    (1...5).each do |skip|
      enumerate_by_popularity(days_by_popularity) do |days|
        guesses << @klass.new.tap do |schedule|
          schedule.kind = :monthly
          schedule.start_date = @start_date
          schedule.end_date = @end_date
          schedule.skip = skip
          schedule.monthly_pattern = days
        end
      end
      
      enumerate_by_popularity(patterns_by_popularity) do |patterns|
        guesses << @klass.new.tap do |schedule|
          schedule.kind = :monthly
          schedule.start_date = @start_date
          schedule.end_date = @end_date
          schedule.skip = skip
          schedule.monthly_pattern = patterns
        end
      end
    end
  end
end

#generate_weekly_guesses(dates) ⇒ Object



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/hiccup/inferable.rb', line 238

def generate_weekly_guesses(dates)
  [].tap do |guesses|
    histogram_of_wdays = dates.to_histogram do |date|
      Date::DAYNAMES[date.wday]
    end
    wdays_by_popularity = histogram_of_wdays.flip
    
    if @verbose
      puts "",
           "  weekly analysis:",
           "    input: #{dates.inspect}",
           "    histogram: #{histogram_of_wdays.inspect}",
           "    by_popularity: #{wdays_by_popularity.inspect}"
    end
    
    (1...5).each do |skip|
      enumerate_by_popularity(wdays_by_popularity) do |wdays|
        guesses << @klass.new.tap do |schedule|
          schedule.kind = :weekly
          schedule.start_date = @start_date
          schedule.end_date = @end_date
          schedule.skip = skip
          schedule.weekly_pattern = wdays
        end
      end
    end
  end
end

#generate_yearly_guesses(dates) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/hiccup/inferable.rb', line 173

def generate_yearly_guesses(dates)
  histogram_of_patterns = dates.to_histogram do |date|
    [date.month, date.day]
  end
  patterns_by_popularity = histogram_of_patterns.flip # => {1 => [...], 2 => [...], 5 => [a, b]}
  highest_popularity = patterns_by_popularity.keys.max # => 5
  most_popular = patterns_by_popularity[highest_popularity].first # => a
  start_date = Date.new(@start_date.year, *most_popular)
  
  [].tap do |guesses|
    (1...5).each do |skip|
      guesses << @klass.new.tap do |schedule|
        schedule.kind = :annually
        schedule.start_date = start_date
        schedule.end_date = @end_date
        schedule.skip = skip
      end
    end
  end
end

#pick_best_guess(guesses, dates) ⇒ Object



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/hiccup/inferable.rb', line 282

def pick_best_guess(guesses, dates)
  scored_guesses = guesses \
    .map { |guess| [guess, score_guess(guess, dates)] } \
    .sort_by { |(guess, score)| -score.to_f }
  
  if @verbose
    puts "\nGUESSES FOR #{dates}:"
    scored_guesses.each do |(guess, score)|
      puts "  (%.3f p/%.3f b/%.3f c/%.3f) #{guess.humanize}" % [
        score.to_f,
        score.prediction_rate,
        score.brick_penalty,
        score.complexity_penalty]
    end
    puts ""
  end
  
  scored_guesses.first
end

#predicted?(date) ⇒ Boolean

Returns:

  • (Boolean)


152
153
154
# File 'lib/hiccup/inferable.rb', line 152

def predicted?(date)
  @schedule && @schedule.contains?(date)
end

#score_guess(guess, input_dates) ⇒ Object



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/hiccup/inferable.rb', line 302

def score_guess(guess, input_dates)
  predicted_dates = guess.occurrences_between(guess.start_date, guess.end_date)
  
  # prediction_rate is the percent of input dates predicted
  predictions = (predicted_dates & input_dates).length
  prediction_rate = Float(predictions) / Float(input_dates.length)
  
  # bricks are dates predicted by this guess but not in the input
  bricks = (predicted_dates - input_dates).length
  
  # brick_rate is the percent of bricks to predictions
  # A brick_rate >= 1 means that this guess bricks more than it predicts
  brick_rate = Float(bricks) / Float(input_dates.length)
  
  # complexity measures how many rules are necesary
  # to describe the pattern
  complexity = complexity_of(guess)
  
  # complexity_rate is the number of rules per inputs
  complexity_rate = Float(complexity) / Float(input_dates.length)
  
  Score.new(prediction_rate, brick_rate, complexity_rate)
end

#start!Object Also known as: restart!



132
133
134
135
136
# File 'lib/hiccup/inferable.rb', line 132

def start!
  @dates = []
  @schedule = nil
  @confidence = 0
end