n 回サイコロを振った後に特定の合計が得られる確率。ルビー

概要

n 個のサイコロの合計の確率を求めるための最良の解決策は何ですか? 見つけることで解決しています

これが私がこれまでやってきたことです。

# 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 つの独立した確率分布の結果を加算することは、2 つの分布を畳み込むことと同じです。分布が離散的である場合、それは離散畳み込みです。

したがって、1 つのサイコロが次のように表されるとします。

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 の 2 つの結果を畳み込みます。有理数を使用すると、浮動小数点精度の問題を回避できます。

詳細を 1 つ省略しましたが、確率が何に一致するかを知るために、初期オフセット (通常の 6 面サイコロの場合は +1) を保存し、それを加算する必要があります。

私は、Rational ではなく浮動小数点で、Gem games_dice でこのロジックのより洗練されたバージョンを作成しました。これは、他のいくつかのサイコロの組み合わせに対応できます。

上記のアプローチを素朴な方法 (一度に 1 つずつサイコロの効果を組み合わせるだけ) を使用してメソッドを基本的に書き直すと、次のようになります。

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 までの完全な確率分布を計算するため、メソッドを 1 回呼び出して配列 (オフセット +100) を 1 回保存するだけで済み、より大きくなる確率など、他の便利な操作を実行できることに注意してください。ある数字。 Ruby では有理数を使用しているため、すべてが完璧な精度で行われます。

あなたの状況ではサイコロの種類が 1 種類しかないため、最後まで Rational の使用を避け、整数 (基本的には結合された値の数) だけを使用し、組み合わせの合計数 (辺の数の乗) で割ることができます。ロールズ)。これははるかに高速で、1 秒以内に数百のサイコロの値を返します。

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