lib/games_dice/probabilities.rb in games_dice-0.2.2 vs lib/games_dice/probabilities.rb in games_dice-0.2.3
- old
+ new
@@ -1,89 +1,133 @@
-# utility class for calculating with probabilities for reuslts from GamesDice objects
+# 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.
+#
+# @example Distribution for a six-sided die
+# probs = GamesDice::Probabilities.for_fair_die( 6 )
+# probs.min # => 1
+# probs.max # => 6
+# probs.expected # => 3.5
+# probs.p_ge( 4 ) # => 0.5
+#
+# @example Adding two distributions
+# pd6 = GamesDice::Probabilities.for_fair_die( 6 )
+# probs = GamesDice::Probabilities.add_distributions( pd6, pd6 )
+# probs.min # => 2
+# probs.max # => 12
+# probs.expected # => 7.0
+# probs.p_ge( 10 ) # => 0.16666666666666669
+#
class GamesDice::Probabilities
- # prob_hash is a Hash with each key as an Integer, and the associated value being the probability
- # of getting that value. It is not validated. Avoid using the default constructor if
- # one of the factory methods or calculation methods already does what you need.
+
+ # Creates new instance of GamesDice::Probabilities.
+ # @param [Hash] prob_hash A hash representation of the distribution, each key is an integer result,
+ # and the matching value is probability of getting that result
+ # @return [GamesDice::Probabilities]
def initialize( prob_hash = { 0 => 1.0 } )
- # This should *probably* be validated in future
+ # This should *probably* be validated in future, but that would impact performance
@ph = prob_hash
end
- # the Hash representation of probabilities. TODO: Hide this from public interface, but make it available
- # to factory methods
+ # @!visibility private
+ # the Hash representation of probabilities.
attr_reader :ph
- # a clone of probability data (as provided to constructor), safe to pass to methods that modify in place
+ # 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.
+ # @return [Hash]
def to_h
@ph.clone
end
+ # @!attribute [r] min
+ # Minimum result in the distribution
+ # @return [Integer]
def min
(@minmax ||= @ph.keys.minmax )[0]
end
+ # @!attribute [r] max
+ # Maximum result in the distribution
+ # @return [Integer]
def max
(@minmax ||= @ph.keys.minmax )[1]
end
- # returns mean expected value as a Float
+ # @!attribute [r] expected
+ # Expected value of distribution.
+ # @return [Float]
def expected
@expected ||= @ph.inject(0.0) { |accumulate,p| accumulate + p[0] * p[1] }
end
- # returns Float probability fram Range (0.0..1.0) that a value chosen from the distribution will
- # be equal to target integer
+ # Probability of result equalling specific target
+ # @param [Integer] target
+ # @return [Float] in range (0.0..1.0)
def p_eql target
@ph[ Integer(target) ] || 0.0
end
- # returns Float probability fram Range (0.0..1.0) that a value chosen from the distribution will
- # be a number greater than target integer
+ # Probability of result being greater than specific target
+ # @param [Integer] target
+ # @return [Float] in range (0.0..1.0)
def p_gt target
p_ge( Integer(target) + 1 )
end
- # returns Float probability fram Range (0.0..1.0) that a value chosen from the distribution will
- # be a number greater than or equal to target integer
+ # Probability of result being equal to or greater than specific target
+ # @param [Integer] target
+ # @return [Float] in range (0.0..1.0)
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] = @ph.select {|k,v| target <= k}.inject(0.0) {|so_far,pv| so_far + pv[1] }
end
- # returns probability than a roll will produce a number less than or equal to target integer
+ # Probability of result being equal to or less than specific target
+ # @param [Integer] target
+ # @return [Float] in range (0.0..1.0)
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] = @ph.select {|k,v| target >= k}.inject(0.0) {|so_far,pv| so_far + pv[1] }
end
- # returns probability than a roll will produce a number less than target integer
+ # Probability of result being less than specific target
+ # @param [Integer] target
+ # @return [Float] in range (0.0..1.0)
def p_lt target
p_le( Integer(target) - 1 )
end
- # constructor returns probability distrubution for a simple fair die
+ # Distribution for a die with equal chance of rolling 1..N
+ # @param [Integer] sides Number of sides on die
+ # @return [GamesDice::Probabilities]
def self.for_fair_die sides
sides = Integer(sides)
raise ArgumentError, "sides must be at least 1" unless sides > 0
h = {}
p = 1.0/sides
(1..sides).each { |x| h[x] = p }
GamesDice::Probabilities.new( h )
end
- # adding two probability distributions calculates a new distribution, representing what would
- # happen if you created a random number using the sum of numbers from both distributions
+ # Combines two distributions to create a third, that represents the distribution created when adding
+ # results together.
+ # @param [GamesDice::Probabilities] pd_a First distribution
+ # @param [GamesDice::Probabilities] pd_b Second distribution
+ # @return [GamesDice::Probabilities]
def self.add_distributions pd_a, pd_b
h = {}
pd_a.ph.each do |ka,pa|
pd_b.ph.each do |kb,pb|
kc = ka + kb
@@ -92,12 +136,17 @@
end
end
GamesDice::Probabilities.new( h )
end
- # adding two probability distributions calculates a new distribution, representing what would
- # happen if you created a random number using the sum of numbers from both distributions
+ # Combines two distributions with multipliers to create a third, that represents the distribution
+ # created when adding weighted results together.
+ # @param [Integer] m_a Weighting for first distribution
+ # @param [GamesDice::Probabilities] pd_a First distribution
+ # @param [Integer] m_b Weighting for second distribution
+ # @param [GamesDice::Probabilities] pd_b Second distribution
+ # @return [GamesDice::Probabilities]
def self.add_distributions_mult m_a, pd_a, m_b, pd_b
h = {}
pd_a.ph.each do |ka,pa|
pd_b.ph.each do |kb,pb|
kc = m_a * ka + m_b * kb
@@ -106,6 +155,6 @@
end
end
GamesDice::Probabilities.new( h )
end
-end # class Dice
+end # class GamesDice::Probabilities