published on in discrete mathematics programming

Permutations and combinations, and ways to calculate the number of draws in a lottery

With a recent, unusually high Power Ball jackpot of ZAR ~220 million, we could not help discussing one’s chances for actually hitting the jackpot as a bit of nice office banter.

Despite your personal take on the Power Ball and Lotto,  we can turn it into a nice mathematical puzzle, and sanatise the gambling aspect away. Yeah, we can do that, because we are into maths, programming and data! For the uninitiated among us, the way it works is simple really. You choose 5 numbers, from a set of  numbers ranging from 1 to 50, and one number from a set of numbers from 1 to 20 - the “Power Ball”. Order does not matter.

So, how do we calculate how many possible options is available to you? I use the term “options” deliberately, since the proper maths terms is actually “combinations”. Note that the maths terminology is contrary to commons sense: where order of selection matters, maths-speak calls it a permutation, and where order does not matter, a combination. Confusing, since we generally do not speak of the permutation of the security alarm code! Away with maths terminology! OK, only until the end of this post.

  • For picking number one, you can choose from 50 options.
  • For number two, from 49 options.
  • For number three, from 48 options, and so on
  • At number five only 46 options remain to choose from.
  • For picking the Power Ball, there are always 20 options.

So we end up with this:

NumberOfPowerBallDraws = 50 * 49 * 48 * 47 * 46 * 20 
NumberOfPowerBallDraws  = 5085024000 

Hmm, that is very big, too big  wouldn’t you say? At those odds, jackpots would be very seldom.

What is wrong? Well, suppose we chose option 1 for our first choice, and then option 2 for our second, that is all fine, but what if we then chose option 2 for our first choice, and option 1 for our second? In our equation above, both those would be valid start choices, but in actual fact, since order does not matter, those are the same choices according to the definition of the game: we are double counting. So how badly are we double counting? Well, as badly as you can choose to order five numbers in a row, so that order matters, and that is kind of what we figured out in our mistake above, giving:

WaysToOrderFiveNumbers = 1 * 2 * 3 * 4 * 5 
WaysToOrderFiveNumbers = 120 

Finally,  to fix our error, we reduce our previous answer by this factor, to get:

CorrectNumberOfPowerBallDraws = NumberOfPowerBallDraws / WaysToOrderFiveNumbers 

CorrectNumberOfPowerBallDraws = 5085024000 / 120 

CorrectNumberOfPowerBallDraws = 42375200 

Is this correct now? Yeah, go ahead and Google it!

OK, back to the maths terms, because it helps us actually.

The multiplication of N numbers, from 1 to N is called factorial, and denoted $N!$, with $0!$ defined as 1. A permutations is an ordered selection of  k items from a set of N items, given by:

$$ P(N,k) = N! / (N - k)! $$

A combination is an unordered selection of k items from a set of N items, and given by:

$$ C(N,k) = N! / (N - k)! * k! $$

It all boils down to: don’t play Power Ball, or any other such lotteries since the odds are firmly stacked against you.

Solutions in a couple of languages

Here are a couple of ways to calculate the figure above.

Python

Using the names from the analysis above, we get a rather long solution:

import functools as ft # for reduce

# n: the number of options, p: how many "power ball" options there are, k: the number of allowed numbers that can be chosen
def numberOfPowerBallDraws(n,p,k):
    return ft.reduce(lambda a,b: a*b, range(n-k+1,n+1)) * p

def waysToOrderKNumbers(k):
    return ft.reduce(lambda a,b: a*b, range(1,k+1))

def correctNumberOfPowerBallDraws(n,p,k):
    return numberOfPowerBallDraws(n,p,k) / waysToOrderKNumbers(k)

p, n, k = 20, 50, 5
print(correctNumberOfPowerBallDraws(n,p,k))
# output 42375200.0

Some points to note:

  1. The function range(a,b) produce a closed, open interval from a through b, so b itself is not included.
  2. The reduce function needs to be imported from functools since it is not part of the base Python system in the REPL anymore.

We can simplify the code into one line, which is less readable but more concise:

import functools as ft
p, n, k = 20, 50, 5
ft.reduce(lambda a,b: a*b, range(n-k+1,n+1)) * p / ft.reduce(lambda a,b: a*b, range(1,k+1))
# output 42375200.0

Noticing that we multiply lists in two places, we can simplify even more by adding a mul(r) product function like this.

import functools as ft

def mul(r): return ft.reduce(lambda a,b: a*b, r)

p, n, k = 20, 50, 5
mul(range(n-k+1,n+1)) * p / mul(range(1,k+1))
# output 42375200.0

Much better, but still to me at least, not as clean as it could be.

Haskell

Prelude> let draws n p k = product [n, (n-1) .. (n-k+1)] * p / product [1 .. k]
Prelude> draws 50 20 5
-- outputs 4.23752e7

In my opinion, the simplest, most straightforward, most beautiful code.