'How can I use a recursive loop to find the amount of times a value shows up between rolling any amount of dice with any amount of sides?

I am trying to write a function that prints out a dictionary that shows the specific amount of values a combination of same-sided dice can possibly roll.

Example output for three 6-sided dice

input: 6,3
Desired output: {3: 1, 4: 3, 5: 6, 6: 10, 7: 15, 8: 21, 9: 25, 10: 27, 11: 27, 12: 25, 13: 21, 14: 15, 15: 10, 16: 6, 17: 3, 18: 1}

Example output for four 8-sided dice

input: 8,4
desired output: {4: 1, 5: 4, 6: 10, 7: 20, 8: 35, 9: 56, 10: 84, 11: 120, 12: 161, 13: 204, 14: 246, 15: 284, 16: 315, 17: 336, 18: 344, 19: 336, 20: 315, 21: 284, 22: 246, 23: 204, 24: 161, 25: 120, 26: 84, 27: 56, 28: 35, 29: 20, 30: 10, 31: 4, 32: 1}

Here is my function that creates an empty dictionary for each combination:

def dice_range(dice_type,dice_count):
    dice_dict = {i+1:0 for i in range(dice_type*dice_count) if i+1 >= dice_count}
    return dice_dict

input: dice_range(8,3)
actual output: {3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 0, 17: 0, 18: 0, 19: 0, 20: 0, 21: 0, 22: 0, 23: 0, 24: 0}

With that I am able to figure out how to solve for a specific number of dice_count of any dice_type(# of sides) Here is the solution for the first couple of examples:

dice_type = 6
dice_count = 3
temp = dice_range(dice_type,dice_count)
# for 3 dice of any number of sides
for x in range(1,dice_type+1):
    for i in range(1,dice_type+1):
        for j in range(1,dice_type+1):
            for k,v in temp.items():
                if x+i+j == k:
                    temp[k] +=1
               
        
dice_type = 8
dice_count = 4
temp = dice_range(dice_type,dice_count)
# for 4 dice of any number of sides
for y in range(1,dice_type+1):
    for x in range(1,dice_type+1):
        for i in range(1,dice_type+1):
            for j in range(1,dice_type+1):
                for k,v in temp.items():
                    if y+x+i+j == k:
                        temp[k] +=1

I hope that is enough context for the question.

Here is what I tried out, but for some reason the recursion is not working as I intended.


def dice_range(dice_type,dice_count):
    dice_dict = {i+1:0 for i in range(dice_type*dice_count) if i+1 >= dice_count}
    return dice_dict

def dice_value_calculator(dice_type,dice_count):
    
    PREDICTION = dice_range(dice_type,dice_count)
    
    # Tallies the number by 1 everytime it shows up in the loop.
    def fill_dict(total):
        for k in PREDICTION.keys():
            if total == k:
                PREDICTION[k] += 1
        
    def loop(temp_dice_count,loop_total = 0):
        
        for loop_i in range(1,(dice_type+1)):

            # Base Case | if the number of dice(dice_count) reaches 0 that means it reached the end of the recursion
            if temp_dice_count == 0:
                fill_dict(loop_total)
                print(PREDICTION)
                loop_total = 0
                
            # General Case / Recursive
            else: 
                loop_total += loop_i
                temp_dice_count -= 1
                loop(temp_dice_count, loop_total)
                
    loop(dice_count) # calls the loop function 
    return PREDICTION # returns the temp dictionary

print(dice_value_calculator(6,3))

This is the output I'm getting for three 6-sided dice

{3: 2, 4: 4, 5: 0, 6: 2, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 0, 17: 0, 18: 0}

Both the fill_dict() function and the dice_range() function work well on their own so it must be how I'm calling loop()

Any tips would be greatly appreciated. Please let me know if I need to clarify anything, this is my first time asking a question so I may have missed something.

Thanks in advance



Solution 1:[1]

You can use a mathematical formula for getting the count of such arrangements where lower bound, upper bound, and number of groups are described.

For the given case, we can take:

  • Number of dice available = number of groups = k
  • A die can have the least number as = 1
  • A die can have the maximum number = Number of faces it has got = upper limit = m

Then, the dictionary needed can be obtained as:

from math import comb
def get_i(n,k,m):
    for i in range(n+1):
        if (n - k + i*m >= 0) and (n-k - i*m - m < 0):
            return i

def s_nkm(n,k,m):
    s=0
    for r in range( get_i(n,k,m) + 1):
        s+=(-1)**r * comb(k,r) * comb(n-r*m - 1, k-1)
    return s

def get_dict(m,k):
    d={}
    for n in range(k, m*k + 1):
        # n is total number of ones
        d[n] = s_nkm(n,k,m)
    return d

m,k = 8,4

print(get_dict(m,k))

Outputs:

{4: 1, 5: 4, 6: 10, 7: 20, 8: 35, 9: 56, 10: 84, 11: 120, 12: 161, 13: 204, 14: 246, 15: 284, 16: 315, 17: 336, 18: 344, 19: 336, 20: 315, 21: 284, 22: 246, 23: 204, 24: 161, 25: 120, 26: 84, 27: 56, 28: 35, 29: 20, 30: 10, 31: 4, 32: 1}

The mathematical equation is added below: enter image description here

Here, grouping is done based on thinking that those indistinguishable items are ones(as we do in partitions)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1