Search code examples
pythonstatisticsdice

I'm trying to do a program that shows the sample space of throwing N dices


I'm trying to do a program that shows the sample space of throwing N dices and write the result of each dice, then sum the results of each dice of each element of the sample space and finally make a histogram with the sums.

I know how to do it for small number of dices, putting by hand for loops.

Example for 3 dices:

import numpy as np
import matplotlib.pyplot as plt

diceNum=3       # number of dices
ss = 6**diceNum # number of elements of the sample space: throw n dices and write points of each dice. (n**N)

diceSum = []
diceThrow = []

nn=0 # to count the number of elements

# 3 for loops for each dice
for k in range(6):
   
   for i in range(6):
   
       for j in range(6):
           
           dices = [k+1,i+1,j+1]   # Result of throw the 3 dices
           diceThrow.append(dices) # Sample space 
           diceSum.append(sum(diceThrow[nn])) #Sum each element of the sample space
       
           nn=nn+1


print(diceThrow)
print(" ")
print(diceSum)

# Make a histogram with the sum of each element of the sample space
plt.hist(diceSum, bins = sm)
plt.grid()
plt.show()

My question is how to do this for N dices, for example N=100, without putting by hand N for loops? Any idea for this algorithm?


Solution

  • To make a little more intuitive code as Bill has mentioned:

    diceNum=10     # number of dices
    ss = 6**diceNum # number of elements of the sample space: throw n dices and write points of each dice. (n**N)
    
    diceSum = []
    diceThrow = []
    
    
    def roll_dice(diceThrow_n1)->list: # diceThrow_n1 is the list for n dices thrown
        #check if it's the first dice
        if diceThrow_n1 == []:
            diceThrow_n2=np.array([[i] for i in range(1,7)])
        else:
            diceThrow_n2 = [] # list for n+1 thrown dices
            for d in diceThrow_n1:
                for t in range(1,7): # throw the n+1 dice
                    diceThrow_n2.append([*d,t])
        return diceThrow_n2
    
    for d in range(diceNum): # Throw diceNum dices
        diceThrow = roll_dice(diceThrow)
    
    diceSum = [sum(elm) for elm in diceThrow] # Sum each element of the sample space
    

    roll_dice takes your diceThrown list does nothing other than take all List entrys and add the end the result of another dice throw. (so every time the function is executed, the entries in diceThrow is multiplied by 6).

    Let us check this for diceNum = 2: In the first execute d = 0 (first dice throw), we provide roll_dice a empty list (diceThrow = []). So roll_dice populate diceThrow with 1 ...6 (diceThrow = [[1],[2],[3],[4],[5],[6]]). So now d = 1: roll_dice starts with diceThrow = [[1],[2],[3],[4],[5],[6]]: so we go to the else part of the function. Now we iterate over all entries in the list. Starting with d = [1]. (* in front of a list gives all elements (*[a,b,c]=a,b,c)) It takes every thrown dice and add the result of the next throw. So [1] gets to [[1,1],[1,2],[1,3],...,[1,6]]. But 1 was only one possible result of the first throw, so we do this for every other possible result and end so with:

    diceThrow = [[1 1]
     [1 2]
     [1 3]
     [1 4]
     [1 5]
     [1 6]
     [2 1]
     [2 2]
     [2 3]
     [2 4]
     [2 5]
     [2 6]
     [3 1]
     [3 2]
     [3 3]
     [3 4]
     [3 5]
     [3 6]
     [4 1]
     [4 2]
     [4 3]
     [4 4]
     [4 5]
     [4 6]
     [5 1]
     [5 2]
     [5 3]
     [5 4]
     [5 5]
     [5 6]
     [6 1]
     [6 2]
     [6 3]
     [6 4]
     [6 5]
     [6 6]]
    

    This can be repeated now for every additional dice. At the end we sum about every entry as you did before, but instead doing this every time we create a new entry in diceThrow we do it at the end to prevent us doing this multiple times. But here the problems begins. This is very ineffective, because lists in python are not the fastest. And we do this again and again. create a list, create a bigger list, ....

    A better way but much less intuitive is using numpy.

    diceNum=2
    diceThrow = np.array(np.meshgrid(*([np.array([1,2,3,4,5,6])]*diceNum))).T.reshape(-1,diceNum)
    diceSum = [sum(elm) for elm in diceThrow]
    

    In principle you give the meshgrid function diceNum np.arrays with 1,2,...,6] and reshape it afterwards in a way you gain the diceNum dices. A good explanation(and a little inspiration for me xD) for whats going on here, you can find here Numpy: efficient way to generate combinations from given ranges and Using numpy to build an array of all combinations of two arrays But even with this I get into long runtimes with diceNum > 10. Maybe anyone else has a good idea to keep the practical approach and not using any analytic theories.