Home > Mobile >  How to get sum of probabilities of rolling w dice where one is fair and the other one is unfair?
How to get sum of probabilities of rolling w dice where one is fair and the other one is unfair?

Time:09-25

I am writing a little program and wanted to ask how I can add the logic of having an unfair dice in the game? Right now, my code produces the sum of probabilities of rolling 2 dices with 6 faces for i times. However, it is treating the dices with a 1/6 probability of rolling a given number. How do I tweak it, so that the unfair dice ONLY shows up in the range of 2-5 but never as 1 or 6? The output should the sum of probs for all numbers in range 2-12 given the fair and unfair dice.

import random
from collections import defaultdict

def main():
    dice = 2
    sides = 6
    rolls = int(input("Enter the number of rolls to simulate: "))
    result = roll(dice, sides, rolls)
    maxH = 0
    for i in range(dice, dice * sides   1):
        if result[i] / rolls > maxH: maxH = result[i] / rolls
    for i in range(dice, dice * sides   1):
        print('{:2d}{:10d}{:8.2%} {}'.format(i, result[i], result[i] / rolls, '#' * int(result[i] / rolls / maxH * 40)))


def roll(dice, sides, rolls):
    d = defaultdict(int)
    for _ in range(rolls):
        d[sum(random.randint(1, sides) for _ in range(dice))]  = 1
    return d

main()

Output

Enter the number of rolls to simulate: 10000
 2       265   2.65% ######
 3       567   5.67% #############
 4       846   8.46% ####################
 5      1166  11.66% ############################
 6      1346  13.46% ################################
 7      1635  16.35% ########################################
 8      1397  13.97% ##################################
 9      1130  11.30% ###########################
10       849   8.49% ####################
11       520   5.20% ############
12       279   2.79% ######

CodePudding user response:

Given that the logic of which results are possible is currently being controlled by the line

random.randint(1, sides)

that's the line to change if you want to roll with different bounds. For example, to get 2-5, you could generalize the function:

def main():
    dice = 2
    sides = 6
    unfair_min = 2
    unfair_max = 5
    rolls = int(input("Enter the number of rolls to simulate: "))
    result_unfair = roll_biased(dice, sides, rolls, min_roll=unfair_min, max_roll=unfair_max)
    maxH = max(result_unfair.values()) / rolls

    for i in range(dice, dice * sides   1):
        print('{:2d}{:10d}{:8.2%} {}'.format(i, result_unfair[i], result_unfair[i] / rolls,
                                             '#' * int(result_unfair[i] / rolls / maxH * 40)))


def roll_biased(dice, sides, rolls, min_roll=1, max_roll=None):
    if max_roll is None:
        max_roll = sides
    d = defaultdict(int)
    for _ in range(rolls):
        d[sum(random.randint(min_roll, max_roll) for _ in range(dice))]  = 1
    return d

Which could print:

Enter the number of rolls to simulate: 10000
 2         0   0.00% 
 3         0   0.00% 
 4       632   6.32% ##########
 5      1231  12.31% ###################
 6      1851  18.51% #############################
 7      2480  24.80% ########################################
 8      1873  18.73% ##############################
 9      1296  12.96% ####################
10       637   6.37% ##########
11         0   0.00% 
12         0   0.00% 

You could also generalize this to arbitrary choices (or arbitrary weights) using random.choices() as such:

def roll_from_choices(dice, sides, rolls, allowed_rolls=None):
    if allowed_rolls is None:
        allowed_rolls = list(range(1, sides 1))
    d = defaultdict(int)

    for _ in range(rolls):
        d[sum(random.choices(allowed_rolls, k=dice))]  = 1
    return d

which you can call as:

result_unfair = roll_from_choices(dice, sides, rolls, allowed_rolls=[2, 3, 4, 5])

CodePudding user response:

I would start with a single function that returns the result of a die(a) roll, where the options available can be tailored to exclude the impossible, something like:

import random

def throw_die(options):
    return random.choice(options)

Then I would code for the generalised case where you can have any number of dice, each of varying abilities (to be passed as the options when throwing the die). In your particular case, that would be two dice with the second excluding 1 and 6(b):

dice = [
    [1, 2, 3, 4, 5, 6],
    [   2, 3, 4, 5   ]
]

Then allocate enough storage for the results (I've wasted a small amount of space here to ensure code for collecting data is much simpler):

min_res = sum([min(die) for die in dice]) # Smallest possible result,
max_res = sum([max(die) for die in dice]) #   and largest.
count = [0] * (max_res   1)               # Allocate space   extra.

Your data collection is then the relatively simple (I've hard-coded the roll count here rather than use input, but you can put that back in):

rolls = 10000 # rolls = int(input("How many rolls? "))
for _ in range(rolls):
    # Throw each die, sum the results, then increment correct count.

    result = sum([throw_die(die) for die in dice])
    count[result]  = 1

And the data output can be done as (rounding rather than truncating so that highest count has forty hashes - that's just my CDO(c) nature kicking in):

hash_mult = 40 / max(count)
for i in range(min_res, max_res   1):
    per_unit = count[i] / rolls
    hashes = "#" * int(count[i] * hash_mult   0.5)
    print(f"{i:2d}{count[i]:10d}{per_unit:8.2%} {hashes}")

The complete program then becomes:

import random

# Throw a single die.

def throw_die(options):
    return random.choice(options)

# Define all dice here as list of lists.
# No zero/negative number allowed, will
#   probably break code :-)

dice = [
    [1, 2, 3, 4, 5, 6],
    [   2, 3, 4, 5   ]
]

# Get smallest/largest possible result.

min_res = sum([min(die) for die in dice])
max_res = sum([max(die) for die in dice])

# Some elements wasted (always zero) to simplify later code.
# Example: throwing three normal dice cannot give 0, 1, or 2.

count = [0] * (max_res   1)

# Do the rolls and collect results.

rolls = 10000
for _ in range(rolls):
    result = sum([throw_die(die) for die in dice])
    count[result]  = 1

# Output all possible results.

hash_mult = 40 / max(count)
for i in range(min_res, max_res   1):
    per_unit = count[i] / rolls
    hashes = "#" * int(count[i] * hash_mult   0.5)
    print(f"{i:2d}{count[i]:10d}{per_unit:8.2%} {hashes}")

and a few sample runs to see it in action:

pax:/mnt/c/Users/Pax/Documents/wsl> python prog.py
 3       418   4.18% #########
 4       851   8.51% ####################
 5      1266  12.66% ##############################
 6      1681  16.81% ########################################
 7      1606  16.06% ######################################
 8      1669  16.69% #######################################
 9      1228  12.28% #############################
10       867   8.67% ####################
11       414   4.14% #########

pax:/mnt/c/Users/Pax/Documents/wsl> python prog.py
 3       450   4.50% ##########
 4       825   8.25% ###################
 5      1206  12.06% ############################
 6      1655  16.55% #######################################
 7      1679  16.79% ########################################
 8      1657  16.57% #######################################
 9      1304  13.04% ###############################
10       826   8.26% ###################
11       398   3.98% #########

pax:/mnt/c/Users/Pax/Documents/wsl> python prog.py
 3       394   3.94% #########
 4       838   8.38% ####################
 5      1271  12.71% ##############################
 6      1617  16.17% ######################################
 7      1656  16.56% #######################################
 8      1669  16.69% ########################################
 9      1255  12.55% ##############################
10       835   8.35% ####################
11       465   4.65% ###########

Footnotes:

(a) Using the correct nomenclature for singular die and multiple dice, in case any non-English speakers are confused.

(b) You could also handle cases like [1, 2, 3, 4, 4, 5, 6] where you're twice as likely to get a 4 as any other numbers. Anything much more complex than that would probably better be handled with a tuple representing each possible result and its relative likelihood. Probably a little too complex to put in a footnote (given it's not a requirement of the question) but you can always ask about this in a separate question if you're interested.

(c) Just like OCD but in the Correct Damn Order :-)

  • Related