Solution: Tossing Coins
Contents
Solution: Tossing Coins#
Write a function that works as a coin-flipper simulator. Then, write another function that calls the coin flipper function several times to determine the approximate probability of getting a certain result combination for
2
tosses (regardless of order).
Function definition#
Coin Flipper Simulator#
Coin type: 2 faces:
head
andtail
.Coin is fair: equal chance of flipping
head
ortail
.The function must return a
string
value corresponding to the tossed face (head
ortail
).You can use any
random function
from therandom
module.
from random import choice
def coin_flipper():
coin = choice(['head','tail'])
return coin
Probability calculator#
Function that accepts
two
string
inputs corresponding to the desired tossed faces, where:The first input is necessary;
If no second input is given, it should should default to an empty string.
Function returns a
float
value between0
and1
.Avoid using any knowledge of combinatorics to solve this problem. Instead, take advantage of the fact that the machine can simulate millions of tosses (samples) in a very short time.
Since this is based on random draws, the probability will be slightly different each time the code is run. So, the greater the number of samples, the more consistent the results.
Option 1: Readable solution#
def get_probability(toss1, toss2=''):
N = 100000 # Define number of repetitions
p = 0 # initialise a probability counter
for _ in range(N):
coin1 = coin_flipper() # Flip one coin
coin2 = coin_flipper() # Flip another coin
# Check if you have the desired combination (evaluate all possible orders!)
if coin1 == toss1:
if coin2 == toss2 or toss2=='':
p+=1
elif coin2 == toss1:
if coin1 == toss2 or toss2=='':
p+=1
return p/N
Option 2: using just one if
#
It is possible concatenate the tosses results in one string:
head
andtail
=headtail
tail
andhead
=tailhead
head
andhead
=headhead
tail
andtail
=tailtail
Note that as the order is not relevant,
headtail
should be counted astailhead
(or vice versa).
This approach makes it easy to expand the function to incorporate more than two tosses without increasing the number of if
statements.
Note that in this solution, only one
if
is used instead of four as in the previous solution.
def get_probability(toss1, toss2=''):
N = 100000 # Define number of repetitions
p = [] # initialise an empty probability list
for _ in range(N):
coin1 = coin_flipper() # Flip one coin
coin2 = coin_flipper() # Flip another coin
# Define a string for double toss
two_toss = "".join(sorted([coin1, coin2])) # Sorted, so tailhead = headtail
desired = "".join(sorted([toss1, toss2])) # Sorted, so tailhead = headtail
if desired in two_toss:
p.append(1) # two_toss and desired were sorted, so no need for a second if
p = sum(p)/N
return p
Option 3: one line solution#
Note that in the solution above the probability counter is a list
rather than an integer
.
This change isn’t necessary, and to be honest, it doesn’t imply better performance. However, working with a list allows us to turn the entire for
loop into a List Comprehension
.
The next proposed solution is exactly the same as the previous one, but uses List Comprehension which allows us to cluster everything in one line:
It’s beautiful? NO
Is it easier to read? NO
It’s fun? DEFINITELY…. (at least I think)
def get_probability(toss1, toss2=''):
return sum([1 for _ in range(100000) if "".join(sorted([toss1, toss2])) in "".join(sorted([coin_flipper(), coin_flipper()]))])/100000
Testing#
Check if your function returns the expected value using the cell below.
import unittest
class UnitTests(unittest.TestCase):
def test_coin_type(self):
self.assertTrue(isinstance(coin_flipper(), str), 'The function should return a string')
def test_coin_head_tail(self):
self.assertTrue(sorted(set(coin_flipper() for _ in range(100000))) == ['head', 'tail'], 'The function should return `head` or `tail`.')
def test_coin_fairness(self):
counts = [coin_flipper() for _ in range(100000)].count('head')/100000
self.assertAlmostEqual(counts, 0.5, places=2, msg=f'The side should be rolled with a probability of 0.50')
def test_probability_type(self):
self.assertTrue(isinstance(get_probability('head','head'), float), 'The function should return a float')
def test_probability_two_heads(self):
self.assertAlmostEqual(get_probability('head','head'), 1/4, places=2, msg='The function should return 0.25 for two heads.')
def test_probability_two_tails(self):
self.assertAlmostEqual(get_probability('tail','tail'), 1/4, places=2, msg='The function should return 0.25 for two tails.')
def test_probability_head_tail(self):
self.assertAlmostEqual(get_probability('head','tail'), 1/2, places=2, msg='The function should return 0.50 for one head and one tail.')
def test_probability_tail_head(self):
self.assertAlmostEqual(get_probability('tail','head'), 1/2, places=2, msg='The function should return 0.50 for one tail and one head.')
def test_probability_atLeast_head(self):
self.assertAlmostEqual(get_probability('head'), 3/4, places=2, msg='The function should return 0.75 for at least one head.')
def test_probability_atLeast_tail(self):
self.assertAlmostEqual(get_probability('tail'), 3/4, places=2, msg='The function should return 0.75 for at least one tail.')
unittest.main(argv=[''], verbosity=2,exit=False)
test_coin_fairness (__main__.UnitTests) ...
ok
test_coin_head_tail (__main__.UnitTests) ...
ok
test_coin_type (__main__.UnitTests) ...
ok
test_probability_atLeast_head (__main__.UnitTests) ...
ok
test_probability_atLeast_tail (__main__.UnitTests) ...
ok
test_probability_head_tail (__main__.UnitTests) ...
ok
test_probability_tail_head (__main__.UnitTests) ...
ok
test_probability_two_heads (__main__.UnitTests) ...
ok
test_probability_two_tails (__main__.UnitTests) ...
ok
test_probability_type (__main__.UnitTests) ...
ok
----------------------------------------------------------------------
Ran 10 tests in 2.175s
OK
<unittest.main.TestProgram at 0x7f0790eb2710>