Pregunta

Para un juego, estoy tratando de determinar la frecuencia con la que un determinado # aparecerá en un # dado de dados que se lanzan. Lo sé ... esa pregunta parece extraña. Déjame intentar explicarlo con números reales.

Entonces, para 1 dado, la frecuencia para cada número será idéntica. 1-6 aparecerá igual número de veces.

Ahora para 2 dados, las cosas se ponen diferentes. Me imagino que 5,6,7 serán los más frecuentes, mientras que los números en ambos extremos del espectro se mostrarán menos o nada (en el caso de 1). Me gustaría saber cómo calcular esta lista y mostrarla en el orden correcto, de más frecuente a menos frecuente.

¿Alguna idea?


@duffymo - Sin embargo, sería realmente bueno tener algún tipo de algoritmo para idearlo. Parece que la forma anterior requerirá mucha selección manual y colocación de números. Si mi recuento de dados es dinámico hasta decir 10, creo que hacerlo a mano será poco eficiente y problemático. :)

¿Fue útil?

Solución

Borrador de una forma recursiva de hacerlo:

public static IEnumerable<KeyValuePair<int, int>> GetFrequenciesByOutcome(int nDice, int nSides)
{
    int maxOutcome = (nDice * nSides);
    Dictionary<int, int> outcomeCounts = new Dictionary<int, int>();
    for(int i = 0; i <= maxOutcome; i++)
        outcomeCounts[i] = 0;

    foreach(int possibleOutcome in GetAllOutcomes(0, nDice, nSides))
        outcomeCounts[possibleOutcome] = outcomeCounts[possibleOutcome] + 1;

    return outcomeCounts.Where(kvp => kvp.Value > 0);
}

private static IEnumerable<int> GetAllOutcomes(int currentTotal, int nDice, int nSides)
{
    if (nDice == 0) yield return currentTotal;
    else
    {
        for (int i = 1; i <= nSides; i++)
            foreach(int outcome in GetAllOutcomes(currentTotal + i, nDice - 1, nSides))
                yield return outcome;
    }
}

A menos que me equivoque, esto debería escupir KeyValuePairs organizados como [clave, frecuencia].

EDITAR : FYI, después de ejecutar esto, muestra las frecuencias de GetFrequenciesByOutcome (2, 6) para ser:

2: 1

3: 2

4: 3

5: 4

6: 5

7: 6

8: 5

9: 4

10: 3

11: 2

12: 1

Otros consejos

Hay 6 * 6 = 36 combinaciones para dos dados.

2 = 1 + 1 solo puede aparecer una vez, por lo que su frecuencia es 1/36. 3 = 1 + 2 o 2 + 1, por lo que su frecuencia es 2/36 = 1/18. 4 = 1 + 3, 2 + 2 o 3 + 1, por lo que su frecuencia es 3/36 = 1/12.

Puedes hacer el resto hasta doce.

Cualquier jugador de backgammon los conoce bien.

No existe un " real; algoritmo " o simulación necesaria: es un cálculo simple basado en una fórmula derivada de De Moivre:

http://www.mathpages.com/home/kmath093.htm

Y no es una & "; curva de campana &"; o distribución normal.

Sume la matriz de frecuencia de los rollos anteriores, 'número de lado' veces cambiando su posición, luego obtendrá la matriz de frecuencias que cada número muestra.

1, 1, 1, 1, 1, 1  # 6 sides, 1 roll

1, 1, 1, 1, 1, 1
   1, 1, 1, 1, 1, 1
      1, 1, 1, 1, 1, 1
         1, 1, 1, 1, 1, 1
            1, 1, 1, 1, 1, 1
+              1, 1, 1, 1, 1, 1
_______________________________
1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1  # 6 sides, 2 rolls

1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1
   1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1
      1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1
         1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1
            1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1
+              1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1
______________________________________________
1, 3, 6,10,15,21,25,27,27,25,21,15,10, 6, 3, 1  # 6 sides, 3 rolls

Esto es mucho más rápido que la simulación de fuerza bruta, ya que la ecuación simple es la mejor. Aquí está mi implementación de python3.

def dice_frequency(sides:int, rolls:int) -> list:
    if rolls == 1:
        return [1]*sides
    prev = dice_frequency(sides, rolls-1)
    return [sum(prev[i-j] for j in range(sides) if 0 <= i-j < len(prev))
            for i in range(rolls*(sides-1)+1)]

por ejemplo,

dice_frequency(6,1) == [1, 1, 1, 1, 1, 1]
dice_frequency(6,2) == [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]
dice_frequency(6,3) == [1, 3, 6, 10, 15, 21, 25, 27, 27, 25, 21, 15, 10, 6, 3, 1]

Tenga en cuenta que debe usar 'número objetivo - recuento de rollos' como índice de la lista para obtener la frecuencia de cada número. Si desea obtener probabilidades, use 'número de lado' ^ 'recuento de rollos' como denominador.

sides = 6
rolls = 3
freq = dice_frequency(sides,rolls)
freq_sum = sides**rolls
for target in range(rolls,rolls*sides+1):
    index = target-rolls
    if 0 <= index < len(freq):
        print("%2d : %2d, %f" % (target, freq[index], freq[index]/freq_sum))
    else:
        print("%2d : %2d, %f" % (target, 0, 0.0))

Este código funciona

 3 :  1, 0.004630
 4 :  3, 0.013889
 5 :  6, 0.027778
 6 : 10, 0.046296
 7 : 15, 0.069444
 8 : 21, 0.097222
 9 : 25, 0.115741
10 : 27, 0.125000
11 : 27, 0.125000
12 : 25, 0.115741
13 : 21, 0.097222
14 : 15, 0.069444
15 : 10, 0.046296
16 :  6, 0.027778
17 :  3, 0.013889
18 :  1, 0.004630

Hay muchas cosas en línea sobre la probabilidad de dados. Aquí hay un enlace que me ayudó con una pregunta del Proyecto Euler:

http://gwydir.demon.co.uk/jo/probability /calcdice.htm

Factoid puro ...

¿Sabías que el triángulo de Pascal es la distribución de probabilidad de las sumas de N dados de dos lados?

   1 1    - 1 die, 1 chance at 1, 1 chance at 2
  1 2 1   - 2 dice, 1 chance at 2, 2 chances at 3, 1 chance at 4
 1 3 3 1  - 3 dice, 1 chance at 3, 3 chances at 4, 3 chances at 5, 1 chance at 6 
1 4 6 4 1 - etc.

Implementación de JavaScript utilizando la creación dinámica de funciones:

<script>
var f;
function prob(dice, value)
 {
var f_s = 'f = function(dice, value) {var occur = 0; var a = [];';
for (x = 0; x < dice; x++)
 {
f_s += 'for (a[' + x + '] = 1; a[' + x + '] <= 6; a[' + x + ']++) {';
 }
f_s += 'if (eval(a.join(\'+\')) == value) {occur++;}';
for (x = 0; x < dice; x++)
 {
f_s += '}';
 }
f_s += 'return occur;}';
eval(f_s);
var occ = f(dice, value);
return [occ, occ + '/' + Math.pow(6, dice), occ / Math.pow(6, dice)];
 };

alert(prob(2, 12)); // 2 die, seeking 12
                    // returns array [1, 1/36, 0.027777777777777776]
</script>

EDITAR: Más bien decepcionado, nadie señaló esto; tuvo que reemplazar 6 * dice con Math.pow(6, dice). No más errores así ...

Parece haber un misterio que rodea exactamente a " por qué " esto es, y aunque duffymo ha explicado parte de esto, estoy mirando otra publicación que dice:

  

No debería haber ninguna razón por la cual 5, 6 y 7 deberían tirarse más [que 2] ya que el primer lanzamiento del dado es un evento independiente del segundo lanzamiento del dado y ambos tienen la misma probabilidad de 1- 6 de ser enrollado.

Hay un cierto atractivo para esto. Pero es incorrecto ... porque el primer lanzamiento afecta las posibilidades. El razonamiento probablemente se puede hacer más fácilmente a través de un ejemplo.

Digamos que estoy tratando de averiguar si la probabilidad de tirar 2 o 7 es más probable en dos dados. Si saco el primer dado y obtengo un 3, ¿cuáles son mis posibilidades ahora de tirar un total de 7? Obviamente, 1 en 6. ¿Cuáles son mis posibilidades de obtener un total de 2? 0 en 6 ... porque no hay nada que pueda tirar en el segundo dado para que mi total sea 2.

Por esta razón, es muy probable que se lance 7 (...) porque no importa lo que saque en el primer dado, todavía puedo alcanzar el total correcto tirando el número correcto en el segundo dado. 6 y 8 son igualmente ligeramente menos probables, 5 y 9 más, y así sucesivamente, hasta llegar a 2 y 12, igualmente improbable con 1 de cada 36 posibilidades cada uno.

Si traza esto (suma vs probabilidad) obtendrá una bonita curva de campana (o, más precisamente, una aproximación en bloque de una debido a la naturaleza discreta de su experimento).

Después de muchas búsquedas en Internet y stackoverflow, encontré Dr. Matemáticas lo explica bien en una función de trabajo (un enlace en otra respuesta tiene una fórmula incorrecta). Convertí la fórmula del Dr. Math a C # y mis pruebas de nUnit (que habían fallado antes con otros intentos de código) pasaron.

Primero tuve que escribir algunas funciones de ayuda:

  public static int Factorial(this int x)
  {
     if (x < 0)
     {
        throw new ArgumentOutOfRangeException("Factorial is undefined on negative numbers");
     }
     return x <= 1 ? 1 : x * (x-1).Factorial();
  }

Debido a la forma en que elegir funciona en matemáticas, me di cuenta de que podría reducir los cálculos si tuviera una función Factorial sobrecargada con un límite inferior. Esta función puede rescatarse cuando se alcanza el límite inferior.

  public static int Factorial(this int x, int lower)
  {
     if (x < 0)
     {
        throw new ArgumentOutOfRangeException("Factorial is undefined on negative numbers");
     }
     if ((x <= 1) || (x <= lower))
     {
        return 1;
     }
     else
     {
        return x * (x - 1).Factorial(lower);
     }
  }

  public static int Choose(this int n, int r)
  {
     return (int)((double)(n.Factorial(Math.Max(n - r, r))) / ((Math.Min(n - r, r)).Factorial()));
  }

Cuando estaban en su lugar, pude escribir

  public static int WaysToGivenSum(int total, int numDice, int sides)
  {
     int ways = 0;
     int upper = (total - numDice) / sides; //we stop on the largest integer less than or equal to this
     for (int r = 0; r <= upper; r++)
     {
        int posNeg = Convert.ToInt32(Math.Pow(-1.0, r)); //even times through the loop are added while odd times through are subtracted
        int top = total - (r * sides) - 1;
        ways += (posNeg * numDice.Choose(r) * top.Choose(numDice - 1));
     }
     return ways;
  }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top