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:
- The function
range(a,b)
produce a closed, open interval from a through b, so b itself is not included. - 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.