Class: TinyOutcome
- Inherits:
-
Object
- Object
- TinyOutcome
- Defined in:
- lib/tiny_outcome.rb
Overview
TinyOutcomes are used to track a history of binary outcomes to the specified precision. for example:
Outcome.new(128) # tracks 128 historic outcomes
when the number of samples added exceeds the number of samples to track, then the oldest sample is dropped automatically. in this way, a TinyOutcome that tracks 128 samples will start discarding its oldest sample as soon as the 129th sample is added.
TinyOutcomes can also be cold, or warm. warm TinyOutcomes have received a minimum number of historic samples to be considered useful. see #initialize for more information on how warmth works.
Usage:
o = TinyOutcome.new(
128, precision of 128 samples
TinyOutcome::WARM_TWO_THIRDS warms up at 2/3rs of precision
)
o.to_hash convenient way to see what's up
87.times { o << rand(2) }
to_s reveals how we’re doing:
L10 1111000110 w 0.49 84/84::128/128
this tells us that of the 128 precision capacity, we’re currently warmed up because we have the minimum (at least 84 samples) to be considered warmed up (this is also indicated with the lowercase ‘w’).
Constant Summary collapse
- WARM_FULL =
:full- WARM_TWO_THIRDS =
:two_thirds- WARM_HALF =
:half- WARM_ONE_THIRD =
:one_third- WARM_NONE =
:none
Instance Attribute Summary collapse
-
#avg ⇒ Object
readonly
Returns the value of attribute avg.
-
#max ⇒ Object
readonly
Returns the value of attribute max.
-
#min ⇒ Object
readonly
Returns the value of attribute min.
-
#one_count ⇒ Object
readonly
Returns the value of attribute one_count.
-
#precision ⇒ Object
readonly
Returns the value of attribute precision.
-
#probability ⇒ Object
readonly
Returns the value of attribute probability.
-
#samples ⇒ Object
readonly
Returns the value of attribute samples.
-
#value ⇒ Object
readonly
Returns the value of attribute value.
-
#warmth ⇒ Object
readonly
Returns the value of attribute warmth.
-
#warmup ⇒ Object
readonly
Returns the value of attribute warmup.
Instance Method Summary collapse
-
#<<(sample) ⇒ Object
add a sample to the historic outcomes.
-
#cold? ⇒ Boolean
the opposite of warm: a TinyOutcome can only be cold or warm.
-
#collected_samples ⇒ Object
returns the array of collected samples of 1s and 0s.
-
#full? ⇒ Boolean
true if we’ve received at least precision number of samples false otherwise.
-
#initialize(precision, warmup = WARM_FULL) ⇒ TinyOutcome
constructor
precision: the number of historic samples you want to store warmup: defaults to WARM_FULL, lets the user specify how many samples we need in order to consider this Outcome tracker “warm”, i.e.
-
#numeric_value ⇒ Object
converts the array of #collected_samples to a base-10 Integer.
-
#to_hash ⇒ Object
convenient way to see what’s up.
-
#to_s ⇒ Object
L10 = last 10 samples.
-
#update_stats! ⇒ Object
updates, and memoizes, the min/max/avg numbers.
-
#warm? ⇒ Boolean
true if we’ve received at least warmup number of samples false otherwise.
-
#winner_at?(percentage) ⇒ Boolean
true if #probability is >= percentage false otherwise.
-
#winner_at_lately?(percentage, max_samples) ⇒ Boolean
true if #probability is >= percentage false otherwise.
Constructor Details
#initialize(precision, warmup = WARM_FULL) ⇒ TinyOutcome
precision: the number of historic samples you want to store warmup: defaults to WARM_FULL, lets the user specify how many samples we
need in order to consider this Outcome tracker "warm", i.e. it has enough
samples that we can trust the probability output
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/tiny_outcome.rb', line 50 def initialize(precision, warmup=WARM_FULL) @precision = precision @probability = 0.0 @one_count = 0 @samples = 0 @min = 1.0 @max = 0.0 @avg = 0.0 @warmth = 0 @value = [0] * @precision @value_index = 0 @warmup = case warmup when WARM_FULL then precision when WARM_TWO_THIRDS then (precision / 3) * 2 when WARM_HALF then precision / 2 when WARM_ONE_THIRD then precision / 3 when WARM_NONE then 0 else raise "Invalid warmup: #{warmup.inspect}" if (!warmup.is_a?(Integer) || warmup < 1) warmup end end |
Instance Attribute Details
#avg ⇒ Object (readonly)
Returns the value of attribute avg.
29 30 31 |
# File 'lib/tiny_outcome.rb', line 29 def avg @avg end |
#max ⇒ Object (readonly)
Returns the value of attribute max.
29 30 31 |
# File 'lib/tiny_outcome.rb', line 29 def max @max end |
#min ⇒ Object (readonly)
Returns the value of attribute min.
29 30 31 |
# File 'lib/tiny_outcome.rb', line 29 def min @min end |
#one_count ⇒ Object (readonly)
Returns the value of attribute one_count.
29 30 31 |
# File 'lib/tiny_outcome.rb', line 29 def one_count @one_count end |
#precision ⇒ Object (readonly)
Returns the value of attribute precision.
29 30 31 |
# File 'lib/tiny_outcome.rb', line 29 def precision @precision end |
#probability ⇒ Object (readonly)
Returns the value of attribute probability.
29 30 31 |
# File 'lib/tiny_outcome.rb', line 29 def probability @probability end |
#samples ⇒ Object (readonly)
Returns the value of attribute samples.
29 30 31 |
# File 'lib/tiny_outcome.rb', line 29 def samples @samples end |
#value ⇒ Object (readonly)
Returns the value of attribute value.
29 30 31 |
# File 'lib/tiny_outcome.rb', line 29 def value @value end |
#warmth ⇒ Object (readonly)
Returns the value of attribute warmth.
29 30 31 |
# File 'lib/tiny_outcome.rb', line 29 def warmth @warmth end |
#warmup ⇒ Object (readonly)
Returns the value of attribute warmup.
29 30 31 |
# File 'lib/tiny_outcome.rb', line 29 def warmup @warmup end |
Instance Method Details
#<<(sample) ⇒ Object
add a sample to the historic outcomes
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/tiny_outcome.rb', line 84 def <<(sample) raise "Invalid sample: #{sample}" unless sample == 0 || sample == 1 removing_one = full? && @value[(@value_index + 1) % @precision] == 1 @value[@value_index] = sample @value_index = (@value_index + 1) % @precision @warmth += 1 unless warmth == warmup @samples += 1 unless full? # percentage of 1s out of the existing samples # # number of 1s # probabilty = --------------- # total samples @one_count -= 1 if removing_one @one_count += 1 if sample == 1 @probability = @one_count / samples.to_f @value end |
#cold? ⇒ Boolean
the opposite of warm: a TinyOutcome can only be cold or warm
127 128 129 |
# File 'lib/tiny_outcome.rb', line 127 def cold? !warm? end |
#collected_samples ⇒ Object
returns the array of collected samples of 1s and 0s
74 75 76 |
# File 'lib/tiny_outcome.rb', line 74 def collected_samples (full? ? @value.rotate(@value_index) : @value)[..(samples-1)] end |
#full? ⇒ Boolean
true if we’ve received at least precision number of samples false otherwise
133 134 135 |
# File 'lib/tiny_outcome.rb', line 133 def full? samples == precision end |
#numeric_value ⇒ Object
converts the array of #collected_samples to a base-10 Integer
79 80 81 |
# File 'lib/tiny_outcome.rb', line 79 def numeric_value collected_samples.join.to_i(2) end |
#to_hash ⇒ Object
convenient way to see what’s up
162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/tiny_outcome.rb', line 162 def to_hash [:value, :samples, :warmth, :warmup, :warm?, :probability, ].each_with_object({}) do |attr, memo| memo[attr] = send(attr) memo end end |
#to_s ⇒ Object
L10 = last 10 samples
176 177 178 179 |
# File 'lib/tiny_outcome.rb', line 176 def to_s max_backward = [value.to_s(2).length, 10].min "L10 #{value.to_s(2)[-max_backward..-1].rjust(10, '?')} #{warm? ? 'W' : 'c'} #{'%.2f' % probability} #{warmth}/#{warmup}::#{samples}/#{precision}" end |
#update_stats! ⇒ Object
updates, and memoizes, the min/max/avg numbers. if you read the min/max/avg attributes you are getting the MEMOIZED values.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/tiny_outcome.rb', line 139 def update_stats! return if @samples == 0 @min = 1.0 @max = 0.0 @avg = 0.0 sum = 0.0 raw = (full? ? @value.rotate(@value_index) : @value)[..(samples-1)] group_size = [@samples, 100].min num_groups = (@samples - group_size) + 1 raw.each_cons(group_size) do |samples_group| num_ones = samples_group.count(1) / group_size.to_f sum += num_ones @min = num_ones if num_ones < @min @max = num_ones if num_ones > @max end @avg = sum / num_groups.to_f end |
#warm? ⇒ Boolean
true if we’ve received at least warmup number of samples false otherwise
122 123 124 |
# File 'lib/tiny_outcome.rb', line 122 def warm? warmth >= warmup end |
#winner_at?(percentage) ⇒ Boolean
true if #probability is >= percentage false otherwise
108 109 110 |
# File 'lib/tiny_outcome.rb', line 108 def winner_at?(percentage) @probability >= percentage end |
#winner_at_lately?(percentage, max_samples) ⇒ Boolean
true if #probability is >= percentage false otherwise
114 115 116 117 118 |
# File 'lib/tiny_outcome.rb', line 114 def winner_at_lately?(percentage, max_samples) recent_samples = collected_samples.last(max_samples) recent_probability = recent_samples.count(1) / recent_samples.length.to_f recent_probability >= percentage end |