Chapter 8 of the following book explores Random Numbers and Games.
We will go through some of the examples covered in this Chapter. This is not meant to be as comprehensive as that chapter.
# Standard imports
import numpy as np
import matplotlib.pyplot as plt
import math as m
from mpmath import mp, iv
We will make use of two Python modules for random numbers.
First there is Numpy's random
sampling submodule. The features of this submodule are described in the Numpy documentation. We can import this module with:
from numpy import random
This import allows us to write random.function
where we would have previously written np.random.function
. (We are following the imports used by L.)
Second there is the random
module in the standard Python library. This module is documented in the Python library documentation. We import it below with the name random_number
following L to avoid the name conflict with the numpy submodule.
import random as random_number
The possibility of a coin flip is "heads" or "tails". We can simulate this with the random_number.choice
function which chooses a random element of a sequence (list or tuple).
coin_possibilities = ("Heads", "Tails")
for i in range(10):
result = random_number.choice(coin_possibilities)
print("Flipping a coin resulted in {}.".format(result))
We can get a random integer chosen uniformly in the interval $[a,b)$ with the command
random.randint(a, b)
Note that b
is excluded from the possibilites. We can use this for example to simulate the roll of a dice.
Problem
We want to write a class representing the dice rolls in Monopoly, where two dice are used. We'd like the class to keep track of the number of times each sum of the two dice is attained. We'd also like the class to keep track of the number of times doubles was rolled (i.e., when both dice have the same value).
Write a class DicePair
which has several methods:
roll()
which returns a pair of integers in $[1,6]$ (chosen uniformly)roll_count(n)
which returns the number of times the dice roll has summed to n
.doubles_count()
which returns the number of times the dice has rolled doubles.Discussion
We can roll a single dice with the following command:
random.randint(1, 7)
To roll two dice we can do:
d1 = random.randint(1, 7)
d2 = random.randint(1, 7)
(d1, d2)
Once d1
and d2
are computed we can compute the total and decide if doubles were rolled as below.
total = d1 + d2
doubles = d1 == d2
(total, doubles)
Because our class needs to keep track of the number of times total
is rolled and the number of doubles
we need a way to do this.
We can just use an integer doubles_counter
to keep track of the number of times doubles is rolled.
We'll keep track of the number of times each total is rolled using a dictionary roll_counts
. This dictionary will map total
to the the number of times that total was rolled. So that we don't have to initialize all the zeros, we'll assume if total
is not a key in the dictionary then that number has not been rolled.
class DicePair:
def __init__(self):
# Initialize the variables for statistics tracking
# doubles_counter will start at zero.
self._doubles_counter = 0
# roll_counts begins as an empty dictionary.
self._roll_counts = {}
def roll(self):
"""
Return a pair of integers representing a pair of dice rolls.
"""
d1 = random.randint(1, 7)
d2 = random.randint(1, 7)
# Keep track of the number of doubles
if d1 == d2:
self._doubles_counter += 1
total = d1 + d2
# Update the number of times total was rolled.
if total in self._roll_counts:
self._roll_counts[total] += 1
else:
# This is the first time we rolled total
self._roll_counts[total] = 1
return (d1, d2)
def roll_count(self, n):
if n in self._roll_counts:
return self._roll_counts[n]
else:
# n has not been rolled yet!
return 0
def doubles_count(self):
return self._doubles_counter
To test our class, lets roll the dice a bunch of times and see if the statistics make sense.
num_rolls = 10**5
dp = DicePair()
for i in range(num_rolls):
dp.roll()
We should roll doubles $1/6$ of the time.
expected_doubles = num_rolls/6
print("We expect {} doubles.".format( expected_doubles ))
print("We rolled doubles {} times.".format( dp.doubles_count() ))
Outcomes of rolling two dice can be any integer in $[2,12]$. The probability of rolling t
is
$$\frac{6 - |t-7|}{36}.$$
(See this figure for an explaination.)
for total in range(2,13):
expected = (6 - abs(total - 7)) * num_rolls / 36
print("The expected number of times {} is rolled is {:0.1f} ".format(total, expected) +
" and we rolled it {} times.".format( dp.roll_count(total) ))
Outcomes of rolling two dice can be any integer in $[2,12]$. We can plot the dice rolls as a histogram using matplotlib
. I referred to the matplotlib
documentation when drawing this. We expect to see a nice
bins = np.arange(1.5,13.5,1)
weights=np.array([dp.roll_count(t) for t in range(2,13)])
plt.hist(bins[:-1], bins = bins, weights=weights, label="results")
total = np.arange(2,13,1)
plt.plot(total, (6 - abs(total - 7)) * num_rolls / 36, "or", label = "expected")
plt.legend()
plt.show()
(6 - abs(total - 7)) * num_rolls / 36
len(weights)
len([dp.roll_count(t) for t in range(2,13)])
len(np.arange(1.5,12.5,1))
np.arange(1.5,13.5,1)
It is natural to represent a deck of cards as a list. For example a deck of $20$ cards with cards numbered $1$ to $20$ might be represented as:
deck = list(range(1,21))
deck
Both numpy
and the random
module have shuffle
methods for randomly reordering a list. We can do either of the below:
random_number.shuffle(deck)
deck
random.shuffle(deck)
deck
We can select a random elemnt of a list lst
using the command
random_number.choice(lst)
For example if a hat has $3$ red marbles, $2$ blue, and $4$ purple, then we can simulate the choice of a random marble using the following:
hat = 3 * ["red"] + 2 * ["blue"] + 4 * ["purple"]
hat
random_number.choice(hat)
The choice is returned to the list as can be seen below:
hat
If we want to actually remove something from the hat, it might be a better choice to select a random index. Then we can use the pop
method to make and remove our choice.
index = random.randint(len(hat))
marble = hat.pop(index)
print("We pulled {} out of the hat.".format(marble))
hat
Remark I just noticed an annoying difference between Numpy's randint
and the Random modules randint
. Numpy excludes the right endpoint and the Random module includes it!
An issue with this method is that it requires making a large list. Numpy's choice
function allows us to put probabilisitic weights on the elements of a list using a named parameter p
containing the list of probabilities. So, the following code has the same probabilities for the result as the above choice
function.
random.choice(["red", "blue", "purple"], p=[3/9, 2/9, 4/9])