Class: GamesDice::Probabilities

Inherits:
Object
  • Object
show all
Defined in:
lib/games_dice/probabilities.rb,
lib/games_dice/marshal.rb

Overview

This class models probability distributions for dice systems.

An object of this class represents a single distribution, which might be the result of a complex combination of dice.

Examples:

Distribution for a six-sided die

probs = GamesDice::Probabilities.for_fair_die( 6 )
probs.min # => 1
probs.max # => 6
probs.expected # => 3.5
probs.p_ge( 4 ) # => 0.5

Adding two distributions

pd6 = GamesDice::Probabilities.for_fair_die( 6 )
probs = GamesDice::Probabilities.add_distributions( pd6, pd6 )
probs.min # => 2
probs.max # => 12
probs.expected # => 7.0
probs.p_ge( 10 ) # => 0.16666666666666669

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(probs = [1.0], offset = 0) ⇒ GamesDice::Probabilities

Creates new instance of GamesDice::Probabilities.

Parameters:

  • probs (Array<Float>) (defaults to: [1.0])

    Each entry in the array is the probability of getting a result

  • offset (Integer) (defaults to: 0)

    The result associated with index of 0 in the array


32
33
34
35
36
# File 'lib/games_dice/probabilities.rb', line 32

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 = Integer(offset)
end

Instance Attribute Details

#expectedFloat (readonly)

Expected value of distribution.

Returns:

  • (Float)

72
73
74
# File 'lib/games_dice/probabilities.rb', line 72

def expected
  @expected ||= calc_expected
end

#maxInteger (readonly)

Maximum result in the distribution

Returns:

  • (Integer)

65
66
67
# File 'lib/games_dice/probabilities.rb', line 65

def max
  @offset + @probs.count() - 1
end

#minInteger (readonly)

Minimum result in the distribution

Returns:

  • (Integer)

58
59
60
# File 'lib/games_dice/probabilities.rb', line 58

def min
  @offset
end

Class Method Details

.add_distributions(pd_a, pd_b) ⇒ GamesDice::Probabilities

Combines two distributions to create a third, that represents the distribution created when adding results together.

Parameters:

Returns:


178
179
180
181
182
183
184
# File 'lib/games_dice/probabilities.rb', line 178

def self.add_distributions pd_a, pd_b
  check_is_gdp( pd_a, pd_b )
  combined_min = pd_a.min + pd_b.min
  combined_max = pd_a.max + pd_b.max

  add_distributions_internal( combined_min, combined_max, 1, pd_a, 1, pd_b )
end

.add_distributions_mult(m_a, pd_a, m_b, pd_b) ⇒ GamesDice::Probabilities

Combines two distributions with multipliers to create a third, that represents the distribution created when adding weighted results together.

Parameters:

Returns:


193
194
195
196
197
198
199
200
201
# File 'lib/games_dice/probabilities.rb', line 193

def self.add_distributions_mult m_a, pd_a, m_b, pd_b
  check_is_gdp( pd_a, pd_b )
  m_a = Integer(m_a)
  m_b = Integer(m_b)

  combined_min, combined_max = calc_combined_extremes( m_a, pd_a, m_b, pd_b ).minmax

  add_distributions_internal( combined_min, combined_max, m_a, pd_a, m_b, pd_b )
end

.for_fair_die(sides) ⇒ GamesDice::Probabilities

Distribution for a die with equal chance of rolling 1..N

Parameters:

  • sides (Integer)

    Number of sides on die

Returns:

Raises:

  • (ArgumentError)

166
167
168
169
170
171
# File 'lib/games_dice/probabilities.rb', line 166

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

.from_h(prob_hash) ⇒ GamesDice::Probabilities

Creates new instance of GamesDice::Probabilities.

Parameters:

  • prob_hash (Hash)

    A hash representation of the distribution, each key is an integer result, and the matching value is probability of getting that result

Returns:

Raises:

  • (TypeError)

157
158
159
160
161
# File 'lib/games_dice/probabilities.rb', line 157

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

.implemented_inSymbol

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.

Returns:

  • (Symbol)

    Either :c or :ruby


206
207
208
# File 'lib/games_dice/probabilities.rb', line 206

def self.implemented_in
  :ruby
end

Instance Method Details

#each {|result, probability| ... } ⇒ GamesDice::Probabilities

Iterates through value, probability pairs

Yield Parameters:

  • result (Integer)

    A result that may be possible in the dice scheme

  • probability (Float)

    Probability of result, in range 0.0..1.0

Returns:


42
43
44
45
# File 'lib/games_dice/probabilities.rb', line 42

def each
  @probs.each_with_index { |p,i| yield( i+@offset, p ) if p > 0.0 }
  return self
end

#given_ge(target) ⇒ GamesDice::Probabilities

Probability distribution derived from this one, where we know (or are only interested in situations where) the result is greater than or equal to target.

Parameters:

  • target (Integer)

Returns:


129
130
131
132
133
134
135
136
137
# File 'lib/games_dice/probabilities.rb', line 129

def given_ge target
  target = Integer(target)
  target = min if min > target
  p = p_ge(target)
  raise "There is no valid distribution given a result >= #{target}" unless p > 0.0
  mult = 1.0/p
  new_probs = @probs[target-@offset,@probs.count-1].map { |x| x * mult }
  GamesDice::Probabilities.new( new_probs, target )
end

#given_le(target) ⇒ GamesDice::Probabilities

Probability distribution derived from this one, where we know (or are only interested in situations where) the result is less than or equal to target.

Parameters:

  • target (Integer)

Returns:


143
144
145
146
147
148
149
150
151
# File 'lib/games_dice/probabilities.rb', line 143

def given_le target
  target = Integer(target)
  target = max if max < target
  p = p_le(target)
  raise "There is no valid distribution given a result <= #{target}" unless p > 0.0
  mult = 1.0/p
  new_probs = @probs[0..target-@offset].map { |x| x * mult }
  GamesDice::Probabilities.new( new_probs, @offset )
end

#p_eql(target) ⇒ Float

Probability of result equalling specific target

Parameters:

  • target (Integer)

Returns:

  • (Float)

    in range (0.0..1.0)


79
80
81
82
83
# File 'lib/games_dice/probabilities.rb', line 79

def p_eql target
  i = Integer(target) - @offset
  return 0.0 if i < 0 || i >= @probs.count
  @probs[ i ]
end

#p_ge(target) ⇒ Float

Probability of result being equal to or greater than specific target

Parameters:

  • target (Integer)

Returns:

  • (Float)

    in range (0.0..1.0)


95
96
97
98
99
100
101
102
103
# File 'lib/games_dice/probabilities.rb', line 95

def p_ge target
  target = Integer(target)
  return @prob_ge[target] if @prob_ge && @prob_ge[target]
  @prob_ge = {} unless @prob_ge

  return 1.0 if target <= min
  return 0.0 if target > max
  @prob_ge[target] = @probs[target-@offset,@probs.count-1].inject(0.0) {|so_far,p| so_far + p }
end

#p_gt(target) ⇒ Float

Probability of result being greater than specific target

Parameters:

  • target (Integer)

Returns:

  • (Float)

    in range (0.0..1.0)


88
89
90
# File 'lib/games_dice/probabilities.rb', line 88

def p_gt target
  p_ge( Integer(target) + 1 )
end

#p_le(target) ⇒ Float

Probability of result being equal to or less than specific target

Parameters:

  • target (Integer)

Returns:

  • (Float)

    in range (0.0..1.0)


108
109
110
111
112
113
114
115
116
# File 'lib/games_dice/probabilities.rb', line 108

def p_le target
  target = Integer(target)
  return @prob_le[target] if @prob_le && @prob_le[target]
  @prob_le = {} unless @prob_le

  return 1.0 if target >= max
  return 0.0 if target < min
  @prob_le[target] = @probs[0,1+target-@offset].inject(0.0) {|so_far,p| so_far + p }
end

#p_lt(target) ⇒ Float

Probability of result being less than specific target

Parameters:

  • target (Integer)

Returns:

  • (Float)

    in range (0.0..1.0)


121
122
123
# File 'lib/games_dice/probabilities.rb', line 121

def p_lt target
  p_le( Integer(target) - 1 )
end

#repeat_n_sum_k(n, k, kmode = :keep_best) ⇒ GamesDice::Probabilities

Calculates distribution generated by summing best k results of n iterations of the distribution.

Parameters:

  • n (Integer)

    Number of repetitions, must be at least 1

  • k (Integer)

    Number of best results to keep and sum

Returns:


226
227
228
229
230
231
232
233
234
# File 'lib/games_dice/probabilities.rb', line 226

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
  check_keep_mode( kmode )
  repeat_n_sum_k_internal( n, k, kmode )
end

#repeat_sum(n) ⇒ GamesDice::Probabilities

Adds a distribution to itself repeatedly, to simulate a number of dice results being summed.

Parameters:

  • n (Integer)

    Number of repetitions, must be at least 1

Returns:


214
215
216
217
218
219
# File 'lib/games_dice/probabilities.rb', line 214

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
  repeat_sum_internal( n )
end

#to_hHash

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 call to this method.

Returns:

  • (Hash)

51
52
53
# File 'lib/games_dice/probabilities.rb', line 51

def to_h
  GamesDice::Probabilities.prob_ao_to_h( @probs, @offset )
end