lib/games_dice/probabilities.rb in games_dice-0.3.6 vs lib/games_dice/probabilities.rb in games_dice-0.3.7

- old
+ new

@@ -25,11 +25,11 @@ # @param [Integer] offset The result associated with index of 0 in the array # @return [GamesDice::Probabilities] 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 = offset + @offset = Integer(offset) end # @!visibility private # the Array, Offset representation of probabilities. def to_ao @@ -39,11 +39,11 @@ # Iterates through value, probability pairs # @yieldparam [Integer] result A result that may be possible in the dice scheme # @yieldparam [Float] probability Probability of result, in range 0.0..1.0 # @return [GamesDice::Probabilities] this object def each - @probs.each_with_index { |p,i| yield( i+@offset, p ) } + @probs.each_with_index { |p,i| yield( i+@offset, p ) if p > 0.0 } return self end # 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 @@ -154,29 +154,35 @@ # 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 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 # 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 + raise ArgumentError, "sides can be at most 100000" if sides > 100000 GamesDice::Probabilities.new( Array.new( sides, 1.0/sides ), 1 ) end # 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 + 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 new_probs = Array.new( 1 + combined_max - combined_min, 0.0 ) probs_a, offset_a = pd_a.to_ao probs_b, offset_b = pd_b.to_ao @@ -197,10 +203,17 @@ # @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 + 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 @@ -230,10 +243,11 @@ # @param [Integer] n Number of repetitions, must be at least 1 # @return [GamesDice::Probabilities] new distribution 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 revbin = n.to_s(2).reverse.each_char.to_a.map { |c| c == '1' } pd_power = self pd_result = nil max_power = revbin.count - 1 @@ -257,10 +271,12 @@ # @return [GamesDice::Probabilities] new distribution 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 if k >= n return repeat_sum( n ) end new_probs = Array.new( @probs.count * k, 0.0 ) new_offset = @offset * k @@ -292,10 +308,11 @@ end private def check_probs_array probs_array + raise TypeError unless probs_array.is_a?( Array ) probs_array.map!{ |n| Float(n) } total = probs_array.inject(0.0) do |t,x| if x < 0.0 || x > 1.0 raise ArgumentError, "Found probability value #{x} which is not in range 0.0..1.0" end @@ -349,11 +366,13 @@ # Convert hash to array,offset notation def self.prob_h_to_ao h rmin,rmax = h.keys.minmax o = rmin - a = Array.new( 1 + rmax - rmin, 0.0 ) - h.each { |k,v| a[k-rmin] = v } + s = 1 + rmax - rmin + raise ArgumentError, "Range of possible results too large" if s > 1000000 + a = Array.new( s, 0.0 ) + h.each { |k,v| a[k-rmin] = Float(v) } [a,o] end # Convert array,offset notation to hash def self.prob_ao_to_h a, o