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