Class: GamesDice::Probabilities
- Inherits:
-
Object
- Object
- GamesDice::Probabilities
- Defined in:
- lib/games_dice/probabilities.rb,
lib/games_dice/marshal.rb
Overview
This class models probability distributions for dice systems.
An object of this class represents a single distribution, which might be the result of a complex combination of dice.
Instance Attribute Summary collapse
-
#expected ⇒ Float
readonly
Expected value of distribution.
-
#max ⇒ Integer
readonly
Maximum result in the distribution.
-
#min ⇒ Integer
readonly
Minimum result in the distribution.
Class Method Summary collapse
-
.add_distributions(pd_a, pd_b) ⇒ GamesDice::Probabilities
Combines two distributions to create a third, that represents the distribution created when adding results together.
-
.add_distributions_mult(m_a, pd_a, m_b, pd_b) ⇒ GamesDice::Probabilities
Combines two distributions with multipliers to create a third, that represents the distribution created when adding weighted results together.
-
.for_fair_die(sides) ⇒ GamesDice::Probabilities
Distribution for a die with equal chance of rolling 1..N.
-
.from_h(prob_hash) ⇒ GamesDice::Probabilities
Creates new instance of GamesDice::Probabilities.
-
.implemented_in ⇒ Symbol
Returns a symbol for the language name that this class is implemented in.
Instance Method Summary collapse
-
#each {|result, probability| ... } ⇒ GamesDice::Probabilities
Iterates through value, probability pairs.
-
#given_ge(target) ⇒ GamesDice::Probabilities
Probability distribution derived from this one, where we know (or are only interested in situations where) the result is greater than or equal to target.
-
#given_le(target) ⇒ GamesDice::Probabilities
Probability distribution derived from this one, where we know (or are only interested in situations where) the result is less than or equal to target.
-
#initialize(probs = [1.0], offset = 0) ⇒ GamesDice::Probabilities
constructor
Creates new instance of GamesDice::Probabilities.
-
#p_eql(target) ⇒ Float
Probability of result equalling specific target.
-
#p_ge(target) ⇒ Float
Probability of result being equal to or greater than specific target.
-
#p_gt(target) ⇒ Float
Probability of result being greater than specific target.
-
#p_le(target) ⇒ Float
Probability of result being equal to or less than specific target.
-
#p_lt(target) ⇒ Float
Probability of result being less than specific target.
-
#repeat_n_sum_k(n, k, kmode = :keep_best) ⇒ GamesDice::Probabilities
Calculates distribution generated by summing best k results of n iterations of the distribution.
-
#repeat_sum(n) ⇒ GamesDice::Probabilities
Adds a distribution to itself repeatedly, to simulate a number of dice results being summed.
-
#to_h ⇒ Hash
A hash representation of the distribution.
Constructor Details
#initialize(probs = [1.0], offset = 0) ⇒ GamesDice::Probabilities
Creates new instance of GamesDice::Probabilities.
27 28 29 30 31 |
# File 'lib/games_dice/probabilities.rb', line 27 def initialize( probs = [1.0], offset = 0 ) # This should *probably* be validated in future, but that would impact performance @probs = check_probs_array probs.clone @offset = Integer(offset) end |
Instance Attribute Details
#expected ⇒ Float (readonly)
Expected value of distribution.
73 74 75 |
# File 'lib/games_dice/probabilities.rb', line 73 def expected @expected ||= calc_expected end |
#max ⇒ Integer (readonly)
Maximum result in the distribution
66 67 68 |
# File 'lib/games_dice/probabilities.rb', line 66 def max @offset + @probs.count() - 1 end |
#min ⇒ Integer (readonly)
Minimum result in the distribution
59 60 61 |
# File 'lib/games_dice/probabilities.rb', line 59 def min @offset end |
Class Method Details
.add_distributions(pd_a, pd_b) ⇒ GamesDice::Probabilities
Combines two distributions to create a third, that represents the distribution created when adding results together.
179 180 181 182 183 184 185 186 187 188 |
# File 'lib/games_dice/probabilities.rb', line 179 def self.add_distributions pd_a, pd_b unless pd_a.is_a?( GamesDice::Probabilities ) && pd_b.is_a?( GamesDice::Probabilities ) raise TypeError, "parameter to add_distributions is not a GamesDice::Probabilities" end combined_min = pd_a.min + pd_b.min combined_max = pd_a.max + pd_b.max add_distributions_internal combined_min, combined_max, 1, pd_a, 1, pd_b end |
.add_distributions_mult(m_a, pd_a, m_b, pd_b) ⇒ GamesDice::Probabilities
Combines two distributions with multipliers to create a third, that represents the distribution created when adding weighted results together.
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/games_dice/probabilities.rb', line 197 def self.add_distributions_mult m_a, pd_a, m_b, pd_b unless pd_a.is_a?( GamesDice::Probabilities ) && pd_b.is_a?( GamesDice::Probabilities ) raise TypeError, "parameter to add_distributions_mult is not a GamesDice::Probabilities" end m_a = Integer(m_a) m_b = Integer(m_b) combined_min, combined_max = [ m_a * pd_a.min + m_b * pd_b.min, m_a * pd_a.max + m_b * pd_b.min, m_a * pd_a.min + m_b * pd_b.max, m_a * pd_a.max + m_b * pd_b.max, ].minmax add_distributions_internal combined_min, combined_max, m_a, pd_a, m_b, pd_b end |
.for_fair_die(sides) ⇒ GamesDice::Probabilities
Distribution for a die with equal chance of rolling 1..N
167 168 169 170 171 172 |
# File 'lib/games_dice/probabilities.rb', line 167 def self.for_fair_die sides sides = Integer(sides) raise ArgumentError, "sides must be at least 1" unless sides > 0 raise ArgumentError, "sides can be at most 100000" if sides > 100000 GamesDice::Probabilities.new( Array.new( sides, 1.0/sides ), 1 ) end |
.from_h(prob_hash) ⇒ GamesDice::Probabilities
Creates new instance of GamesDice::Probabilities.
158 159 160 161 162 |
# File 'lib/games_dice/probabilities.rb', line 158 def self.from_h prob_hash raise TypeError, "from_h expected a Hash" unless prob_hash.is_a? Hash probs, offset = prob_h_to_ao( prob_hash ) GamesDice::Probabilities.new( probs, offset ) end |
.implemented_in ⇒ Symbol
Returns a symbol for the language name that this class is implemented in. The C version of the code is noticeably faster when dealing with larger numbers of possible results.
216 217 218 |
# File 'lib/games_dice/probabilities.rb', line 216 def self.implemented_in :ruby end |
Instance Method Details
#each {|result, probability| ... } ⇒ GamesDice::Probabilities
Iterates through value, probability pairs
43 44 45 46 |
# File 'lib/games_dice/probabilities.rb', line 43 def each @probs.each_with_index { |p,i| yield( i+@offset, p ) if p > 0.0 } return self end |
#given_ge(target) ⇒ GamesDice::Probabilities
Probability distribution derived from this one, where we know (or are only interested in situations where) the result is greater than or equal to target.
130 131 132 133 134 135 136 137 138 |
# File 'lib/games_dice/probabilities.rb', line 130 def given_ge target target = Integer(target) target = min if min > target p = p_ge(target) raise "There is no valid distribution given a result >= #{target}" unless p > 0.0 mult = 1.0/p new_probs = @probs[target-@offset,@probs.count-1].map { |x| x * mult } GamesDice::Probabilities.new( new_probs, target ) end |
#given_le(target) ⇒ GamesDice::Probabilities
Probability distribution derived from this one, where we know (or are only interested in situations where) the result is less than or equal to target.
144 145 146 147 148 149 150 151 152 |
# File 'lib/games_dice/probabilities.rb', line 144 def given_le target target = Integer(target) target = max if max < target p = p_le(target) raise "There is no valid distribution given a result <= #{target}" unless p > 0.0 mult = 1.0/p new_probs = @probs[0..target-@offset].map { |x| x * mult } GamesDice::Probabilities.new( new_probs, @offset ) end |
#p_eql(target) ⇒ Float
Probability of result equalling specific target
80 81 82 83 84 |
# File 'lib/games_dice/probabilities.rb', line 80 def p_eql target i = Integer(target) - @offset return 0.0 if i < 0 || i >= @probs.count @probs[ i ] end |
#p_ge(target) ⇒ Float
Probability of result being equal to or greater than specific target
96 97 98 99 100 101 102 103 104 |
# File 'lib/games_dice/probabilities.rb', line 96 def p_ge target target = Integer(target) return @prob_ge[target] if @prob_ge && @prob_ge[target] @prob_ge = {} unless @prob_ge return 1.0 if target <= min return 0.0 if target > max @prob_ge[target] = @probs[target-@offset,@probs.count-1].inject(0.0) {|so_far,p| so_far + p } end |
#p_gt(target) ⇒ Float
Probability of result being greater than specific target
89 90 91 |
# File 'lib/games_dice/probabilities.rb', line 89 def p_gt target p_ge( Integer(target) + 1 ) end |
#p_le(target) ⇒ Float
Probability of result being equal to or less than specific target
109 110 111 112 113 114 115 116 117 |
# File 'lib/games_dice/probabilities.rb', line 109 def p_le target target = Integer(target) return @prob_le[target] if @prob_le && @prob_le[target] @prob_le = {} unless @prob_le return 1.0 if target >= max return 0.0 if target < min @prob_le[target] = @probs[0,1+target-@offset].inject(0.0) {|so_far,p| so_far + p } end |
#p_lt(target) ⇒ Float
Probability of result being less than specific target
122 123 124 |
# File 'lib/games_dice/probabilities.rb', line 122 def p_lt target p_le( Integer(target) - 1 ) end |
#repeat_n_sum_k(n, k, kmode = :keep_best) ⇒ GamesDice::Probabilities
Calculates distribution generated by summing best k results of n iterations of the distribution.
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/games_dice/probabilities.rb', line 252 def repeat_n_sum_k n, k, kmode = :keep_best n = Integer( n ) k = Integer( k ) raise "Cannot combine probabilities less than once" if n < 1 # Technically this is a limitation of C code, but Ruby version is most likely slow and inaccurate beyond 170 raise "Too many dice to calculate numbers of arrangements" if n > 170 check_keep_mode( kmode ) if k >= n return repeat_sum( n ) end new_probs = Array.new( @probs.count * k, 0.0 ) new_offset = @offset * k d = n - k each do | q, p_maybe | next unless p_maybe > 0.0 # keep_distributions is array of Probabilities, indexed by number of keepers > q, which is in 0...k keep_distributions = calc_keep_distributions( k, q, kmode ) p_table = calc_p_table( q, p_maybe, kmode ) (0...k).each do |n| keepers = [2] * n + [1] * (k-n) p_so_far = keepers.inject(1.0) { |p,idx| p * p_table[idx] } next unless p_so_far > 0.0 (0..d).each do |dn| discards = [1] * (d-dn) + [0] * dn sequence = keepers + discards p_sequence = discards.inject( p_so_far ) { |p,idx| p * p_table[idx] } next unless p_sequence > 0.0 p_sequence *= GamesDice::Combinations.count_variations( sequence ) kd = keep_distributions[n] kd.each { |r,p_r| new_probs[r-new_offset] += p_r * p_sequence } end end end GamesDice::Probabilities.new( new_probs, new_offset ) end |
#repeat_sum(n) ⇒ GamesDice::Probabilities
Adds a distribution to itself repeatedly, to simulate a number of dice results being summed.
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/games_dice/probabilities.rb', line 224 def repeat_sum n n = Integer( n ) raise "Cannot combine probabilities less than once" if n < 1 raise "Probability distribution too large" if ( n * @probs.count ) > 1000000 pd_power = self pd_result = nil use_power = 1 loop do if ( use_power & n ) > 0 if pd_result pd_result = GamesDice::Probabilities.add_distributions( pd_result, pd_power ) else pd_result = pd_power end end use_power = use_power << 1 break if use_power > n pd_power = GamesDice::Probabilities.add_distributions( pd_power, pd_power ) end pd_result end |
#to_h ⇒ Hash
A hash representation of the distribution. Each key is an integer result, and the matching value is probability of getting that result. A new hash is generated on each call to this method.
52 53 54 |
# File 'lib/games_dice/probabilities.rb', line 52 def to_h GamesDice::Probabilities.prob_ao_to_h( @probs, @offset ) end |