84 lines
2.8 KiB
Ruby
84 lines
2.8 KiB
Ruby
module Math
|
||
|
||
def self.factorial(n)
|
||
raise ArgumentError, 'The argument muse be a natural Fixnum N' if not n.is_a? Fixnum
|
||
raise Math::DomainError, 'The argument must be a natural (out of domain -- factorial)' if n < 0
|
||
return 1 if n.zero?
|
||
return (1..n).inject(:*)
|
||
end unless self.method_defined? :factorial
|
||
|
||
def self.coef_binomial(n, k)
|
||
return 0 if n < 0 or k < 0 or n < k
|
||
return factorial(n) / (factorial(k) * factorial(n - k))
|
||
end
|
||
|
||
# @note if no named parameters are used, then it will try to use the unamed parameters (tries, success, probability)
|
||
def self.binomial_distribution(*unamed, tries: nil, success: nil, probability: nil)
|
||
unamed.reverse! # pop works on a stack
|
||
Binomial.new(tries: tries || unamed.pop, probability: probability || unamed.pop).distribute(success || unamed.pop)
|
||
end
|
||
|
||
class SuperiorityError < Exception; end
|
||
end
|
||
|
||
class Array
|
||
def second
|
||
self[1]
|
||
end unless self.method_defined? :second
|
||
end
|
||
|
||
class Binomial
|
||
|
||
# @param n [Fixnum] number of tries
|
||
# @param p [Float] probability of success
|
||
# @param tries [Fixnum] alias for n
|
||
# @param probability [Float] alias for p
|
||
# @param failure [Float] alias for `p = 1 - failure`
|
||
# @note if no probability is defined, the default value will be 0.5
|
||
def initialize n: nil, p: nil, tries: nil, probability: nil, failure: nil
|
||
tries_count = n || tries
|
||
probability = p || probability || (failure && 1.0 - failure) || 0.5
|
||
raise ArgumentError, "The argument `n` `#{tries_count}` is not an Integer" unless tries_count.is_a? Integer
|
||
raise ArgumentError, "The argument `p` `#{probability}` is not a Numeric" unless probability.is_a? Numeric
|
||
tries_count = Float(tries_count)
|
||
raise Math::DomainError, "The argument `n` `#{tries_count}` is not in greater or equal to 0" if tries_count < 0.0
|
||
raise Math::DomainError, "The argument `p` `#{probability}` is not in greater or equal to 0" if probability < 0.0
|
||
raise Math::DomainError, "The argument `p` `#{probability}` is not in lesser or equal to 1" if probability > 1.0
|
||
@n = tries_count.to_i
|
||
@p = probability.to_f
|
||
end
|
||
|
||
def to_s
|
||
"#<#{self.class} @n=#@n, @p=#@p>"
|
||
end
|
||
|
||
# @param k [Fixnum] number of test successful.
|
||
# @param k [Enumerable] list of number of test successful.
|
||
# @return [Float] probability
|
||
def distribute k
|
||
if k.is_a? Enumerable
|
||
distribute_enumerable k
|
||
elsif k.is_a? Integer
|
||
distribute_integer k
|
||
else
|
||
raise ArgumentError
|
||
end
|
||
end
|
||
|
||
private def distribute_enumerable k
|
||
k.map{|p| distribute_integer(p) }.reduce(&:+)
|
||
end
|
||
|
||
private def distribute_integer k
|
||
raise Math::SuperiorityError, "the number of success must be lesser or equal to the number of tries (#{@n})" if k > @n
|
||
Math.coef_binomial(@n, k) * @p**k * (1 - @p) ** (@n - k)
|
||
end
|
||
|
||
end
|
||
|
||
#
|
||
# if __FILE__ == $0
|
||
# require 'pry'
|
||
# binding.pry
|
||
# end
|