# Little class to help us with hypergeometric probabilities.
#
# LICENSE: This code is freeware. You may use this code for any purpose you
# like so long as this license is attached. If this code is used in a
# commercial application, you must send me an email so I feel uber-geeky. No
# other compensation is required. Email me at jechols@nerdbucket.com
class HypergeometricProbabilities
attr_accessor :marbles, :white, :picks
def initialize(options)
@marbles = options[:marbles]
@white = options[:white]
@picks = options[:picks]
end
# Computes the number of ways you can represent all the white marbles -
# since marble order doesn't matter, this tells us how many ways we could
# draw all the white marbles and still get a perfect "win". In a
# lottery-specific situation this is also useful for the probability calc,
# but since this has expanded to all hypergeometric distributions, this
# function is really just useful for informational purposes.
def get_white_permutations
return combin(@marbles, @white)
end
# Computes the number of ways to draw all our picks. Useful for information
# as well as the final probability calc.
def get_pick_permutations
return combin(@marbles, @picks)
end
# Yields # of matches, probability of occurring for each possibility of
# matches, from [white, picks].min down to zero. Useful for letting our
# caller spew data, store it in a hash, array, whatever.
def get_all_odds
# Get total permutations once. Yeah, optimizing slow Ruby code is silly,
# but this is an easy optimization.
picked_perms = get_pick_permutations
# Calculate matches down to 0
[@white, @picks].min.downto 0 do |matched|
# Ways to arrange white marble pulls to win
white_wins = combin(@white, matched)
# Ways to pull the black marbles to win
black_wins = combin(@marbles - @white, @picks - matched)
# Probability is the multiplication of white * black permutations,
# divided by total permutations. To win you must have one of the white
# marble combos and one of the black marble combos. So the number of
# permutations that give you both are just the multiplication of the
# white and black permutations. It's almost easy to understand!
# Get total permutations for getting this number of matches
# If we have a nil value, we know something went "wrong" and
# probability is 0
probability = 0
unless white_wins.nil? || black_wins.nil?
probability = (white_wins * black_wins) / picked_perms.to_f
end
yield(matched, probability)
end
end
private
# Slow but effective recursive factorial function. This could use a lookup
# table and be MUCH faster, but I don't really care much until I actually
# need this class to be fast (in which case I'll probably not use Ruby)
def factorial(num)
return nil if num < 0
return 1 if num <= 1
return num * factorial(num - 1)
end
# Basic combination function. Very slow as it uses the recursive factorial
# function. But oh well.
def combin(n, k)
return 0 if (n < k || n < 0 || k < 0)
return factorial(n) / (factorial(n - k) * factorial(k) )
end
end