lib/games_dice/bunch.rb in games_dice-0.2.3 vs lib/games_dice/bunch.rb in games_dice-0.2.4

- old
+ new

@@ -156,16 +156,12 @@ # @return [GamesDice::Probabilities] Probability distribution of bunch. def probabilities return @probabilities if @probabilities @probabilities_complete = true - # TODO: It is possible to optimise this slightly by combining already-calculated values - # Adding dice is same as multiplying probability sets for that number of dice - # Combine(probabililities_3_dice, probabililities_single_die) == Combine(probabililities_2_dice, probabililities_2_dice) - # It is possible to minimise the total number of multiplications, gaining about 30% efficiency, with careful choices - single_roll_probs = @single_die.probabilities.to_h if @keep_mode && @ndice > @keep_number + single_roll_probs = @single_die.probabilities.to_h preadd_probs = {} single_roll_probs.each { |k,v| preadd_probs[k.to_s] = v } (@keep_number-1).times do preadd_probs = prob_accumulate_combinations preadd_probs, single_roll_probs @@ -178,19 +174,16 @@ preadd_probs.each do |k,v| total = k.split(';').map { |s| s.to_i }.inject(:+) combined_probs[total] ||= 0.0 combined_probs[total] += v end + @probabilities = GamesDice::Probabilities.from_h( combined_probs ) else - combined_probs = single_roll_probs.clone - (@ndice-1).times do - combined_probs = prob_accumulate combined_probs, single_roll_probs - end + @probabilities = GamesDice::Probabilities.repeat_distribution( @single_die.probabilities, @ndice ) end - @probabilities_min, @probabilities_max = combined_probs.keys.minmax - @probabilities = GamesDice::Probabilities.new( combined_probs ) + return @probabilities end # Simulates rolling the bunch of identical dice # @return [Integer] Sum of all rolled dice, or sum of all keepers def roll @@ -268,98 +261,51 @@ explanation end private - # combines two sets of probabilities where the end result is the first set of keys plus - # the second set of keys, at the associated probailities of the values - def prob_accumulate first_probs, second_probs - accumulator = Hash.new - - first_probs.each do |v1,p1| - second_probs.each do |v2,p2| - v3 = v1 + v2 - p3 = p1 * p2 - accumulator[v3] ||= 0.0 - accumulator[v3] += p3 - end - end - - accumulator - end - # combines two sets of probabilities, as above, except tracking unique permutations def prob_accumulate_combinations so_far, die_probs, keep_rule = nil accumulator = Hash.new + accumulator.default = 0.0 so_far.each do |sig,p1| combo = sig.split(';').map { |s| s.to_i } case keep_rule when nil then die_probs.each do |v2,p2| - new_sig = (combo + [v2]).sort.join(';') + new_sig = (combo + [v2]).sort!.join(';') p3 = p1 * p2 - accumulator[new_sig] ||= 0.0 accumulator[new_sig] += p3 end when :keep_best then need_more_than = combo.min + len = combo.size die_probs.each do |v2,p2| if v2 > need_more_than - new_sig = (combo + [v2]).sort[1..combo.size].join(';') + new_sig = (combo + [v2]).sort![1,len].join(';') else new_sig = sig end p3 = p1 * p2 - accumulator[new_sig] ||= 0.0 accumulator[new_sig] += p3 end when :keep_worst then need_less_than = combo.max + len = combo.size die_probs.each do |v2,p2| if v2 < need_less_than - new_sig = (combo + [v2]).sort[0..(combo.size-1)].join(';') + new_sig = (combo + [v2]).sort![0,len].join(';') else new_sig = sig end p3 = p1 * p2 - accumulator[new_sig] ||= 0.0 accumulator[new_sig] += p3 end end end accumulator - end - - # Generates all sets of [throw_away,may_keep_exactly,keep_preferentially,combinations] that meet - # criteria for correct total number of dice and keep dice. These then need to be assessed for every - # die value by the caller to get a full set of probabilities - def generate_item_counts total_dice, keep_dice - # Constraints are: - # may_keep_exactly must be at least 1, and at most is all the dice - # keep_preferentially plus may_keep_exactly must be >= keep_dice, but keep_preferentially < keep dice - # sum of all three always == total_dice - item_counts = [] - (1..total_dice).each do |may_keep_exactly| - min_kp = [keep_dice - may_keep_exactly, 0].max - max_kp = [keep_dice - 1, total_dice - may_keep_exactly].min - (min_kp..max_kp).each do |keep_preferentially| - counts = [ total_dice - may_keep_exactly - keep_preferentially, may_keep_exactly, keep_preferentially ] - counts << combinations(counts) - item_counts << counts - end - end - item_counts - end - - # How many unique ways can a set of items, some of which are identical, be arranged? - def combinations item_counts - item_counts = item_counts.map { |i| Integer(i) }.select { |i| i > 0 } - total_items = item_counts.inject(:+) - numerator = 1.upto(total_items).inject(:*) - denominator = item_counts.map { |i| 1.upto(i).inject(:*) }.inject(:*) - numerator / denominator end end # class Bunch