Birthday Paradox
The Birthday Paradox, also called the Birthday Problem, is the surprisingly high probability that two people will have the same birthday even in a small group of people. In a group of 70 people, there’s a 99.9 percent chance of two people having a matching birthday. But even in a group as small as 23 people, there’s a 50 percent chance of a matching birthday. This program performs several probability experiments to determine the percentages for groups of different sizes. We call these types of experiments, in which we conduct multiple random trials to understand the likely outcomes, Monte Carlo experiments.
You can find out more about the Birthday Paradox at https://en.wikipedia.org/wiki/Birthday_problem.
birthday_paradox.py
1
2"""Birthday Paradox Simulation, by Al Sweigart al@inventwithpython.com
3Explore the surprising probabilities of the "Birthday Paradox".
4More info at https://en.wikipedia.org/wiki/Birthday_problem
5This code is available at https://nostarch.com/big-book-small-python-programming
6Tags: short, math, simulation"""
7
8import datetime, random
9
10
11def getBirthdays(numberOfBirthdays):
12 """Returns a list of number random date objects for birthdays."""
13 birthdays = []
14 for i in range(numberOfBirthdays):
15 # The year is unimportant for our simulation, as long as all
16 # birthdays have the same year.
17 startOfYear = datetime.date(2001, 1, 1)
18
19 # Get a random day into the year:
20 randomNumberOfDays = datetime.timedelta(random.randint(0, 364))
21 birthday = startOfYear + randomNumberOfDays
22 birthdays.append(birthday)
23 return birthdays
24
25
26def getMatch(birthdays):
27 """Returns the date object of a birthday that occurs more than once
28 in the birthdays list."""
29 if len(birthdays) == len(set(birthdays)):
30 return None # All birthdays are unique, so return None.
31
32 # Compare each birthday to every other birthday:
33 for a, birthdayA in enumerate(birthdays):
34 for b, birthdayB in enumerate(birthdays[a + 1 :]):
35 if birthdayA == birthdayB:
36 return birthdayA # Return the matching birthday.
37
38
39# Display the intro:
40print('''Birthday Paradox, by Al Sweigart al@inventwithpython.com
41
42The birthday paradox shows us that in a group of N people, the odds
43that two of them have matching birthdays is surprisingly large.
44This program does a Monte Carlo simulation (that is, repeated random
45simulations) to explore this concept.
46
47(It's not actually a paradox, it's just a surprising result.)
48''')
49
50# Set up a tuple of month names in order:
51MONTHS = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
52 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
53
54while True: # Keep asking until the user enters a valid amount.
55 print('How many birthdays shall I generate? (Max 100)')
56 response = input('> ')
57 if response.isdecimal() and (0 < int(response) <= 100):
58 numBDays = int(response)
59 break # User has entered a valid amount.
60print()
61
62# Generate and display the birthdays:
63print('Here are', numBDays, 'birthdays:')
64birthdays = getBirthdays(numBDays)
65for i, birthday in enumerate(birthdays):
66 if i != 0:
67 # Display a comma for each birthday after the first birthday.
68 print(', ', end='')
69 monthName = MONTHS[birthday.month - 1]
70 dateText = '{} {}'.format(monthName, birthday.day)
71 print(dateText, end='')
72print()
73print()
74
75# Determine if there are two birthdays that match.
76match = getMatch(birthdays)
77
78# Display the results:
79print('In this simulation, ', end='')
80if match != None:
81 monthName = MONTHS[match.month - 1]
82 dateText = '{} {}'.format(monthName, match.day)
83 print('multiple people have a birthday on', dateText)
84else:
85 print('there are no matching birthdays.')
86print()
87
88# Run through 100,000 simulations:
89print('Generating', numBDays, 'random birthdays 100,000 times...')
90input('Press Enter to begin...')
91
92print('Let\'s run another 100,000 simulations.')
93simMatch = 0 # How many simulations had matching birthdays in them.
94for i in range(100000):
95 # Report on the progress every 10,000 simulations:
96 if i % 10000 == 0:
97 print(i, 'simulations run...')
98 birthdays = getBirthdays(numBDays)
99 if getMatch(birthdays) != None:
100 simMatch = simMatch + 1
101print('100,000 simulations run.')
102
103# Display simulation results:
104probability = round(simMatch / 100000 * 100, 2)
105print('Out of 100,000 simulations of', numBDays, 'people, there was a')
106print('matching birthday in that group', simMatch, 'times. This means')
107print('that', numBDays, 'people have a', probability, '% chance of')
108print('having a matching birthday in their group.')
109print('That\'s probably more than you would think!')