In this excercise, we want to simulate the outcome of rolling dice. We will walk through several levels of building up funcitonality.
Let's create a function that will return a random value between one and six that emulates the outcome of the roll of one die. Python has a random number package called random.
In [ ]:
import random
def single_die():
"""Outcome of a single die roll"""
return random.randint(1,6)
In [ ]:
for _ in range(50):
print(single_die(),end=' ')
In [ ]:
def dice_roll(dice_count):
"""Outcome of a rolling dice_count dice
Args:
dice_count (int): number of dice to roll
Returns:
int: sum of face values of dice
"""
out = 0
for _ in range(dice_count):
out += single_die()
return out
In [ ]:
for _ in range(100):
print(dice_roll(2), end=' ')
In [ ]:
def dice_rolls(dice_count, rolls_count):
"""Return list of many dice rolls
Args:
dice_count (int): number of dice to roll
rolls_count (int): number of rolls to do
Returns:
list: list of dice roll values.
"""
out = []
for _ in range(rolls_count):
out.append(dice_roll(dice_count))
return out
print(dice_rolls(2,100))
In [ ]:
import pylab as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = (10, 4)
def dice_histogram(dice_count, rolls_count, bins):
"""Plots outcome of many dice rolls
Args:
dice_count (int): number of dice to roll
rolls_count (int): number of rolls to do
bins (int): number of histogram bins
"""
plt.hist(dice_rolls(dice_count, rolls_count),bins)
plt.show()
dice_histogram(2, 10000, 200)
In [ ]:
dice_histogram(100, 10000, 200)
In [ ]:
import time
start = time.time()
dice_histogram(100, 10000, 200)
print(time.time()-start, 'seconds')
Seems like a long time... Can we make it faster? Yes!
Using lots of loops in python is not usually the most efficient way to accomplish numeric tasks. Instead, we should use numpy. With numpy we can "vectorize" operations and under the hood numpy is doing the computation with C code that has a python interface. We don't have to worry about anything under the hood.
Start by checking out numpy's randint function. Let's rewrite dice_rolls using numpy functions and no loops.
To do this, we are going to use np.random.randint to create a 2-D array of random dice rolls. That array will have dice_count rows and rolls_count columns--ie, the size of the array is (dice_count, rolls_count).
In [ ]:
import numpy as np
np.random.randint(1,7,(2,10))
In [ ]:
np.sum(np.random.randint(1,7,(2,10)),axis=0)
In [ ]:
def dice_rolls_np(dice_count, rolls_count):
"""Return list of many dice rolls
Args:
dice_count (int): number of dice to roll
rolls_count (int): number of rolls to do
Returns:
np.array: list of dice roll values.
"""
return np.sum(
np.random.randint(1,7,(dice_count,rolls_count)),
axis=0)
print(dice_rolls(2,100))
In [ ]:
def dice_histogram_np(dice_count, rolls_count, bins):
"""Plots outcome of many dice rolls
Args:
dice_count (int): number of dice to roll
rolls_count (int): number of rolls to do
bins (int): number of histogram bins
"""
plt.hist(dice_rolls_np(dice_count, rolls_count),bins)
plt.show()
start = time.time()
dice_histogram_np(100, 10000, 200)
print(time.time()-start, 'seconds')
In [ ]:
%timeit dice_rolls_np(100, 1000)
In [ ]:
%timeit dice_rolls(100, 1000)
The improvement in the core function call was two orders of magnitude, but when we timed it initially, we were also waiting for the plot to render which consumed the majority of the time.
In risk two players roll dice in each battle to determine how many armies are lost on each side.
Here are the rules:
Let's make a function that simulates the outcome of one Risk battle and outputs the net score.
The functions we created in the first part of this tutorial are not useful for this task.
In [ ]:
def risk_battle():
"""Risk battle simulation"""
# get array of three dice values
attacking_dice = np.random.randint(1,7,3)
# get array of two dice values
defending_dice = np.random.randint(1,7,2)
# sort both sets and take top two values
attacking_dice_sorted = np.sort(attacking_dice)[::-1]
defending_dice_sorted = np.sort(defending_dice)[::-1]
# are the attacking values greater?
attack_wins = attacking_dice_sorted[:2] > defending_dice_sorted[:2]
# convert boolean values to -1, +1
attack_wins_pm = attack_wins*2 - 1
# sum up these outcomes
return np.sum(attack_wins_pm)
for _ in range(50):
print(risk_battle(), end=' ')
In [ ]:
outcomes = [risk_battle() for _ in range(10000)]
plt.hist(outcomes)
plt.show()
In [ ]:
np.mean([risk_battle() for _ in range(10000)])