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#

  1. Coin type: 2 faces: head and tail.

  2. Coin is fair: equal chance of flipping head or tail.

  3. The function must return a string value corresponding to the tossed face (head or tail).

  4. You can use any random function from the random module.

from random import choice

def coin_flipper():
    coin = choice(['head','tail'])
    return coin

Probability calculator#

  1. 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.

  2. Function returns a float value between 0 and 1.

  3. 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 and tail = headtail

  • tail and head = tailhead

  • head and head = headhead

  • tail and tail = tailtail

Note that as the order is not relevant, headtail should be counted as tailhead (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>