【问题标题】:Probability of getting specific sum after rolling n dice. Ruby掷骰子后获得特定总和的概率。红宝石
【发布时间】:2015-08-11 09:27:42
【问题描述】:

用n个骰子找到滚动和的概率的最佳解决方案是什么? 我正在通过查找来解决它

  1. 意思。
  2. 标准偏差。
  3. z_score 下面的数字x
  4. 上述数字的z_score x
  5. 将两者都转换为概率
  6. 从另一个中减去一个

这是我到目前为止所做的。

# sides - number of sides on one die
def get_mean(sides)
  (1..sides).inject(:+) / sides.to_f
end

def get_variance(sides)
  mean_of_squares = ((1..sides).inject {|sum, side| sum + side ** 2}) / sides.to_f
  square_mean = get_mean(sides) ** 2

  mean_of_squares - square_mean
end

def get_sigma(variance)
  variance ** 0.5
end

# x - the number of points in question
def get_z_score(x, mean, sigma)
  (x - mean) / sigma.to_f
end

# Converts z_score to probability
def z_to_probability(z)
  return 0 if z < -6.5
  return 1 if z > 6.5

  fact_k = 1
  sum = 0
  term = 1
  k = 0

  loop_stop = Math.exp(-23)
  while term.abs > loop_stop do
    term = 0.3989422804 * ((-1)**k) * (z**k) / (2*k+1) / (2**k) * (z**(k+1)) / fact_k
    sum += term
    k += 1
    fact_k *= k
  end

  sum += 0.5
  1 - sum
end

# Calculate probability of getting 'х' total points by rolling 'n' dice with 'sides' number of sides.
def probability_of_sum(x, n, sides=6)

  mean = n * get_mean(sides)
  variance = get_variance(sides)
  sigma = get_sigma(n * variance)

  # Rolling below the sum
  z1 = get_z_score(x, mean, sigma)
  prob_1 = z_to_probability(z1)

  # Rolling above the sum
  z2 = get_z_score(x+1, mean, sigma)
  prob_2 = z_to_probability(z2)

  prob_1 - prob_2
end

# Run probability for 100 dice
puts probability_of_sum(400, 100)

我担心的是,当我选择x = 200时,概率为0。 这是正确的解决方案吗?

【问题讨论】:

  • 如果我理解正确.. 不。如果您将所有 2 掷 100 次,总数将是 200。所以它有一定的可能性发生。
  • 我也是这么想的。但是,根据68-95-99.7 (empirical) rule 或 3-sigma 规则“大约 68% 从正态分布中提取的值在 1 标准偏差 σ i> 偏离平均值;大约 95% 的值在 2 个标准差范围内;大约 99.7%3 个范围内 标准差。”在这种情况下,使用 100 6 面骰子 mean = 350σ = 17。这意味着,99.7% 的值将落在 299401 的范围内。 (350 +/- 17 * 3)
  • 另外请注意,算法存在根本问题,运行非常简单的东西,例如 x=12 和 n=2(应该是 1/36)不起作用。我认为@NeilSlater 可能对离散性有所了解。
  • 在简单的情况下,比如滚动 2 个骰子 Monte Carlo 模拟是更好的解决方案。这种情况不同。
  • 如果您关心结果的准确性,Monte Carlo 并不是一个好的解决方案。对于一个简单的分布,例如相同骰子的总和,可以以完美的精度和比蒙特卡洛更快的任何合理误差进行计算。

标签: ruby math probability standard-deviation


【解决方案1】:

有一个涉及二项式系数的交替和的精确解。我已经在几个地方(QuoraMSE)写出来了,尽管有一些有缺陷的版本,你可以在其他地方找到它。请注意,如果您实现它,您可能需要取消比最终结果大得多的二项式系数,并且如果您使用浮点运算,您可能会失去太多的精度。

Neil Slater 建议使用动态规划来计算卷积是一个很好的建议。它比二项式系数的总和慢,但相当稳健。您可以通过几种方式加速它,例如使用平方乘幂和使用快速傅里叶变换,但很多人会发现这些方法太过分了。

要修正您的方法,您应该对正态近似值使用(简单)连续性校正,并限制在您有足够的骰子并且您正在评估的最大值和最小值与您期望正态近似值的距离足够远的情况下是好的,无论是绝对的还是相对的。连续性校正是将 n 的计数替换为从 n-1/2 到 n+1/2 的区间。

总共滚动 200 的方式数的确切计数是 7745954278770349845682110174816333221135826585518841002760,所以概率是除以 6^100,大约是 1.18563 x 10^-20。

简单连续性校正的正态近似为 Phi((200.5-350)/sqrt(3500/12))-Phi((199.5-350)/sqrt(3500/12)) = 4.2 x 10^-19 .这在绝对意义上是准确的,因为它非常接近于 0,但它偏离了 35 倍,因此相对而言并不是很好。法线近似给出了更接近中心的更好的相对近似。

【讨论】:

    【解决方案2】:

    将两个独立概率分布的结果相加与convolving这两个分布相同。如果分布是离散的,那么它是一个离散卷积。

    所以如果单个骰子表示为:

    probs_1d6 = Array.new(6) { Rational(1,6) }
    

    那么2d6可以这样计算:

    probs_2d6 = []
    probs_1d6.each_with_index do |prob_a,i_a|  
      probs_1d6.each_with_index do |prob_b,i_b| 
        probs_2d6[i_a + i_b] = ( probs_2d6[i_a + i_b] || 0 ) + prob_a * prob_b
      end
    end
    
    probs_2d6
    # =>  [(1/36), (1/18), (1/12), (1/9), (5/36), (1/6), 
    #      (5/36), (1/9), (1/12), (1/18), (1/36)]
    

    虽然这对于骰子的边是 n 平方的,并且完全逻辑组合可以减少这种情况,但对于更复杂的设置,这样做通常不太灵活。这种方法的好处是您可以继续添加更多骰子并进行其他更奇特的组合。例如,要获得 4d6,您可以对 2d6 的两个结果进行卷积。使用有理数可以避免浮点精度问题。

    我跳过了一个细节,您确实需要存储初始偏移量(对于普通的六面模具为 +1)并将其加在一起,以便知道概率匹配。

    我在gem games_dice 中制作了这个逻辑的更复杂版本,使用浮点而不是 Rational,它可以处理其他一些骰子组合。

    以下是使用上述方法以一种幼稚的方式对您的方法进行基本重写(简单地一次组合一个骰子的效果):

    def probability_of_sum(x, n, sides=6)
      return 0 if x < n
      single_die_probs = Array.new(sides) { Rational(1,sides) }
    
      combined_probs = [1] # Represents chance of getting 0 when rolling 0 dice :-)
    
      # This is not the most efficient way to do this, but easier to understand
      n.times do
        start_probs = combined_probs
        combined_probs = []
        start_probs.each_with_index do |prob_a,i_a|  
            single_die_probs.each_with_index do |prob_b,i_b| 
              combined_probs[i_a + i_b] = ( combined_probs[i_a + i_b] || 0 ) + prob_a * prob_b
            end
        end
      end
    
      combined_probs[ x - n ] || 0
    end
    
    puts probability_of_sum(400, 100).to_f
    # => 0.0003172139126369326
    

    注意这个方法实际上是计算100-600的全概率分布,所以你只需要调用一次并存储一次数组(加上偏移+100),你可以做其他有用的事情比如得到概率大于某个数。由于在 Ruby 中使用了Rational 数字,所有这些都具有完美的精度。

    因为在您的情况下,您只有一种骰子,我们可以避免使用 Rational 直到最后,只使用整数(基本上是组合值的计数),然后除以组合总数(边卷数的幂)。这要快得多,并且在一秒钟内返回 100 个骰子的值:

    def probability_of_sum(x, n, sides=6)
      return 0 if x < n
      combined_probs = [1] # Represents chance of getting 0 when rolling 0 dice :-)
    
      n.times do
        start_probs = combined_probs
        combined_probs = []
        start_probs.each_with_index do |prob_a,i_a|  
            sides.times do |i_b| 
              combined_probs[i_a + i_b] = ( combined_probs[i_a + i_b] || 0 ) + prob_a
            end
        end
      end
    
      Rational( combined_probs[ x - n ] || 0, sides ** n )
    end
    

    【讨论】:

    • 只是一个小的澄清。自变量之和的概率密度或质量函数与密度或质量函数的卷积不同,它正是卷积。
    【解决方案3】:

    这是我的最终版本。

    1. get_z_score 中的总和偏移量分别更改为x-0.5x+0.5,以获得更精确的结果。
    2. 添加了return 0 if x &lt; n || x &gt; n * sides 以涵盖情况,其中总和小于骰子数,大于骰子数乘以面数。
    3. 添加了带有结果的基准测试

    主要功能

    # sides - number of sides on one die
    def get_mean(sides)
      (1..sides).inject(:+) / sides.to_f
    end
    
    def get_variance(sides)
      mean_of_squares = ((1..sides).inject {|sum, side| sum + side ** 2}) / sides.to_f
      square_mean = get_mean(sides) ** 2
    
      mean_of_squares - square_mean
    end
    
    def get_sigma(variance)
      variance ** 0.5
    end
    
    # x - the number of points in question
    def get_z_score(x, mean, sigma)
      (x - mean) / sigma.to_f
    end
    
    # Converts z_score to probability
    def z_to_probability(z)
      return 0 if z < -6.5
      return 1 if z > 6.5
    
      fact_k = 1
      sum = 0
      term = 1
      k = 0
    
      loop_stop = Math.exp(-23)
      while term.abs > loop_stop do
        term = 0.3989422804 * ((-1)**k) * (z**k) / (2*k+1) / (2**k) * (z**(k+1)) / fact_k
        sum += term
        k += 1
        fact_k *= k
      end
    
      sum += 0.5
      1 - sum
    end
    
    # Calculate probability of getting 'х' total points by rolling 'n' dice with 'sides' number of sides.
    def probability_of_sum(x, n, sides=6)
      return 0 if x < n || x > n * sides
    
      mean = n * get_mean(sides)
      variance = get_variance(sides)
      sigma = get_sigma(n * variance)
    
      # Rolling below the sum
      z1 = get_z_score(x-0.5, mean, sigma)
      prob_1 = z_to_probability(z1)
    
      # Rolling above the sum
      z2 = get_z_score(x+0.5, mean, sigma)
      prob_2 = z_to_probability(z2)
    
      prob_1 - prob_2
    end
    

    基准测试

    require 'benchmark'
    
    Benchmark.bm do |x|
      x.report { @prob = probability_of_sum(350, 100).to_f }
      puts "\tWith x = 350 and n = 100:"
      puts "\tProbability: #{@prob}"
    end
    puts
    
    Benchmark.bm do |x|
      x.report { @prob = probability_of_sum(400, 100).to_f }
      puts "\tWith x = 400 and n = 100:"
      puts "\tProbability: #{@prob}"
    end
    puts
    
    Benchmark.bm do |x|
      x.report { @prob = probability_of_sum(1000, 300).to_f }
      puts "\tWith x = 1000 and n = 300:"
      puts "\tProbability: #{@prob}"
    end
    

    结果

           user     system      total        real
       0.000000   0.000000   0.000000 (  0.000049)
        With x = 350 and n = 100:
        Probability: 0.023356331366255034
    
           user     system      total        real
       0.000000   0.000000   0.000000 (  0.000049)
        With x = 400 and n = 100:
        Probability: 0.00032186531055478085
    
           user     system      total        real
       0.000000   0.000000   0.000000 (  0.000032)
        With x = 1000 and n = 300:
        Probability: 0.003232390001131513
    

    【讨论】:

    • 您好,感谢您的解决方案!你能解释一下 0.3989422804 值是什么意思,为什么 loop_stop = Math.exp(-23) ?
    • 取自here,取自here。您也可以尝试搜索 z-score 到概率公式 以获得更好的理解。
    【解决方案4】:

    我也用Monte Carlo的方法解决了这个问题,结果比较接近。

    # x - sum of points to find probability for
    # n - number of dice
    # trials - number of trials
    def monte_carlo(x, n, trials=10000)
      pos = 0
    
      trials.times do
        sum = n.times.inject(0) { |sum| sum + rand(1..6) }
        pos += 1 if sum == x
      end
    
      pos / trials.to_f
    end
    
    puts monte_carlo(300, 100, 30000)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-16
      • 1970-01-01
      • 2015-07-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多