It's a million to one chance...

“What d'you mean?” he said.

“Well, all right, last desperate million-to-one chances always work, right, no problem, but. . . well, it's pretty wossname, specific. I mean, isn't it?”

“You tell me,” said Nobby.

“What if it's just a thousand-to-one chance?” said Colon agonisedly.

“What?”

“Anyone ever heard of a thousand-to-one shot coming up?”

Carrot looked up. “Don't be daft, Sergeant,” he said. “No-one ever saw a thousand-to-one chance come up. The odds against it are-” his lips moved- “millions to one.”

Guards! Guards!, Terry Pratchett

Who's going to win? Will it be Colon, with his one-in-a-million shot? Or will the dragon leave him a drifting cloud of ash?

We're going to create a function that randomly says who wins. To outline how this works, we'll define a function that always works one way first.


In [1]:
from numpy.random import rand

def colon_wins(p_colon):
    """
    If a random number is less than `p_colon` then Colon wins, otherwise the dragon does.
    
    Parameters
    ----------
    
    p_colon: scalar
        Probability that Colon wins
        
    Notes
    -----
    
    In this case we do not use a random number, but fix it to be zero: 
    this way, Colon always wins
    """
    
    return 0.0 <= p_colon

Let's see this working 20 times, when the probability of Colon winning is 50%:


In [2]:
for i in range(20):
    if colon_wins(0.5):
        print("Colon wins!")
    else:
        print("What's that ash cloud?")


Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!

Now let's check we still get the same result, even when the probability of Colon winning is only 10%:


In [3]:
for i in range(20):
    if colon_wins(0.1):
        print("Colon wins!")
    else:
        print("What's that ash cloud?")


Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!

Now we'll re-define the function so that it's using proper random numbers:


In [4]:
def colon_wıns(p_colon):
    """
    If a random number is less than `p_colon` then Colon wins, otherwise the dragon does.
    
    Parameters
    ----------
    
    p_colon: scalar
        Probability that Colon wins
    """
    
    return rand() <= p_colon

Now, when called 20 times, when the probability of Colon winning is 50%:


In [5]:
for i in range(20):
    if colon_wıns(0.5):
        print("Colon wins!")
    else:
        print("What's that ash cloud?")


Colon wins!
Colon wins!
Colon wins!
Colon wins!
What's that ash cloud?
Colon wins!
What's that ash cloud?
Colon wins!
Colon wins!
What's that ash cloud?
What's that ash cloud?
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
Colon wins!
What's that ash cloud?
Colon wins!
What's that ash cloud?

And, when called 20 times, when the probability of Colon winning is 10%:


In [6]:
for i in range(20):
    if colon_wıns(0.1):
        print("Colon wins!")
    else:
        print("What's that ash cloud?")


What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
Colon wins!
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
What's that ash cloud?
Colon wins!

Let's check that, as the number of trials is increased, we really get the probability we expect:


In [7]:
trials = 1000000
success = 0
for i in range(trials):
    success += colon_wıns(0.5)
print("50% success in theory: {:.2f}% success in {} trials.".
      format(success/trials*100, trials))


50% success in theory: 50.00% success in 1000000 trials.

In [8]:
trials = 1000000
success = 0
for i in range(trials):
    success += colon_wıns(0.1)
print("10% success in theory: {:.2f}% success in {} trials.".
      format(success/trials*100, trials))


10% success in theory: 9.97% success in 1000000 trials.

Finally, what happens when the probability of Colon winning is minute: 0.0001%, or Pratchett's famous one-in-a-million chance?


In [9]:
trials = 1000000
success = 0
for i in range(trials):
    success += colon_wins(1e-6)
print("One-in-a-million success in theory: {:.2f}% success in {} trials.".
      format(success/trials*100, trials))


One-in-a-million success in theory: 100.00% success in 1000000 trials.

It's a miracle! The function knows Pratchett's rule!