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