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

- old
+ new

@@ -181,22 +181,12 @@ 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 - probs_a.each_with_index do |pa,i| - probs_b.each_with_index do |pb,j| - k = i + j - pc = pa * pb - new_probs[ k ] += pc - end - end - GamesDice::Probabilities.new( new_probs, combined_min ) + add_distributions_internal combined_min, combined_max, 1, pd_a, 1, pd_b end # 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 @@ -215,22 +205,11 @@ 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 - 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 - - probs_a.each_with_index do |pa,i| - probs_b.each_with_index do |pb,j| - k = m_a * (i + offset_a) + m_b * (j + offset_b) - combined_min - pc = pa * pb - new_probs[ k ] += pc - end - end - GamesDice::Probabilities.new( new_probs, combined_min ) + add_distributions_internal combined_min, combined_max, m_a, pd_a, m_b, pd_b end # 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. # @return [Symbol] Either :c or :ruby @@ -244,24 +223,25 @@ # @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 - revbin.each_with_index do |use_power, i| - if use_power + 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 - pd_power = GamesDice::Probabilities.add_distributions( pd_power, pd_power ) unless i == max_power + use_power = use_power << 1 + break if use_power > n + pd_power = GamesDice::Probabilities.add_distributions( pd_power, pd_power ) end pd_result end # Calculates distribution generated by summing best k results of n iterations @@ -273,10 +253,12 @@ 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 @@ -307,10 +289,25 @@ GamesDice::Probabilities.new( new_probs, new_offset ) end private + def self.add_distributions_internal combined_min, combined_max, m_a, pd_a, m_b, pd_b + 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 + + probs_a.each_with_index do |pa,i| + probs_b.each_with_index do |pb,j| + k = m_a * (i + offset_a) + m_b * (j + offset_b) - combined_min + pc = pa * pb + new_probs[ k ] += pc + end + end + GamesDice::Probabilities.new( new_probs, combined_min ) + end + 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 @@ -323,45 +320,45 @@ end probs_array end def calc_keep_distributions k, q, kmode - if kmode == :keep_best - keep_distributions = [ GamesDice::Probabilities.new( [1.0], q * k ) ] - if p_gt(q) > 0.0 && k > 1 - kd_probabilities = given_ge( q + 1 ) - (1...k).each do |n| - extra_o = GamesDice::Probabilities.new( [1.0], q * ( k - n ) ) - n_probs = kd_probabilities.repeat_sum( n ) - keep_distributions[n] = GamesDice::Probabilities.add_distributions( extra_o, n_probs ) - end + kd_probabilities = calc_keep_definite_distributions q, kmode + + keep_distributions = [ GamesDice::Probabilities.new( [1.0], q * k ) ] + if kd_probabilities && k > 1 + (1...k).each do |n| + extra_o = GamesDice::Probabilities.new( [1.0], q * ( k - n ) ) + n_probs = kd_probabilities.repeat_sum( n ) + keep_distributions[n] = GamesDice::Probabilities.add_distributions( extra_o, n_probs ) end - elsif kmode == :keep_worst - keep_distributions = [ GamesDice::Probabilities.new( [1.0], q * k ) ] - if p_lt(q) > 0.0 && k > 1 - kd_probabilities = given_le( q - 1 ) - (1...k).each do |n| - extra_o = GamesDice::Probabilities.new( [1.0], q * ( k - n ) ) - n_probs = kd_probabilities.repeat_sum( n ) - keep_distributions[n] = GamesDice::Probabilities.add_distributions( extra_o, n_probs ) - end - end - else - raise "Keep mode #{kmode.inspect} not recognised" end + keep_distributions end + def calc_keep_definite_distributions q, kmode + kd_probabilities = nil + case kmode + when :keep_best + p_definites = p_gt(q) + kd_probabilities = given_ge( q + 1 ) if p_definites > 0.0 + when :keep_worst + p_definites = p_lt(q) + kd_probabilities = given_le( q - 1 ) if p_definites > 0.0 + end + kd_probabilities + end + def calc_p_table q, p_maybe, kmode - if kmode == :keep_best + case kmode + when :keep_best p_kept = p_gt(q) p_rejected = p_lt(q) - elsif kmode == :keep_worst + when :keep_worst p_kept = p_lt(q) p_rejected = p_gt(q) - else - raise "Keep mode #{kmode.inspect} not recognised" end [ p_rejected, p_maybe, p_kept ] end # Convert hash to array,offset notation @@ -384,9 +381,13 @@ def calc_expected total = 0.0 @probs.each_with_index { |v,i| total += (i+@offset)*v } total + end + + def check_keep_mode kmode + raise "Keep mode #{kmode.inspect} not recognised" unless [:keep_best,:keep_worst].member?( kmode ) end end # class GamesDice::Probabilities # @!visibility private