Skip to content

Commit 0033ca8

Browse files
authored
Add baffling-birthdays (#145)
1 parent af38fcd commit 0033ca8

10 files changed

Lines changed: 378 additions & 0 deletions

File tree

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,14 @@
650650
"prerequisites": [],
651651
"difficulty": 5
652652
},
653+
{
654+
"slug": "baffling-birthdays",
655+
"name": "Baffling Birthdays",
656+
"uuid": "68cc53af-905b-40c9-8456-6bffdf4452a1",
657+
"practices": [],
658+
"prerequisites": [],
659+
"difficulty": 5
660+
},
653661
{
654662
"slug": "binary-search-tree",
655663
"name": "Binary Search Tree",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
return {
2+
default = {
3+
ROOT = { '.' }
4+
}
5+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Instructions
2+
3+
Your task is to estimate the birthday paradox's probabilities.
4+
5+
To do this, you need to:
6+
7+
- Generate random birthdates.
8+
- Check if a collection of randomly generated birthdates contains at least two with the same birthday.
9+
- Estimate the probability that at least two people in a group share the same birthday for different group sizes.
10+
11+
~~~~exercism/note
12+
A birthdate includes the full date of birth (year, month, and day), whereas a birthday refers only to the month and day, which repeat each year.
13+
Two birthdates with the same month and day correspond to the same birthday.
14+
~~~~
15+
16+
~~~~exercism/caution
17+
The birthday paradox assumes that:
18+
19+
- There are 365 possible birthdays (no leap years).
20+
- Each birthday is equally likely (uniform distribution).
21+
22+
Your implementation must follow these assumptions.
23+
~~~~
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Introduction
2+
3+
Fresh out of college, you're throwing a huge party to celebrate with friends and family.
4+
Over 70 people have shown up, including your mildly eccentric Uncle Ted.
5+
6+
In one of his usual antics, he bets you £100 that at least two people in the room share the same birthday.
7+
That sounds ridiculous — there are many more possible birthdays than there are guests, so you confidently accept.
8+
9+
To your astonishment, after collecting the birthdays of just 32 guests, you've already found two guests that share the same birthday.
10+
Accepting your loss, you hand Uncle Ted his £100, but something feels off.
11+
12+
The next day, curiosity gets the better of you.
13+
A quick web search leads you to the [birthday paradox][birthday-problem], which reveals that with just 23 people, the probability of a shared birthday exceeds 50%.
14+
15+
Ah. So _that's_ why Uncle Ted was so confident.
16+
17+
Determined to turn the tables, you start looking up other paradoxes; next time, _you'll_ be the one making the bets.
18+
19+
~~~~exercism/note
20+
The birthday paradox is a [veridical paradox][veridical-paradox]: even though it feels wrong, it is actually true.
21+
22+
[veridical-paradox]: https://en.wikipedia.org/wiki/Paradox#Quine's_classification
23+
~~~~
24+
25+
[birthday-problem]: https://en.wikipedia.org/wiki/Birthday_problem
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"glennj"
4+
],
5+
"files": {
6+
"solution": [
7+
"baffling_birthdays.moon"
8+
],
9+
"test": [
10+
"baffling_birthdays_spec.moon"
11+
],
12+
"example": [
13+
".meta/example.moon"
14+
]
15+
},
16+
"blurb": "Estimate the birthday paradox's probabilities.",
17+
"source": "Erik Schierboom",
18+
"source_url": "https://github.com/exercism/problem-specifications/pull/2539"
19+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
YearDates = [os.date('%m-%d', os.time {year: 2001, month: 1, day: d}) for d = 1, 365]
2+
3+
isLeapYear = (y) -> y % 4 == 0 and (y % 100 != 0 or y % 400 == 0)
4+
5+
randomYear = ->
6+
year = 1900 + math.random 126
7+
isLeapYear(year) and randomYear! or year
8+
9+
randomBirthdate = -> "#{randomYear!}-#{YearDates[math.random #YearDates]}"
10+
11+
sharedBirthday = (birthdates) ->
12+
if #birthdates > 1
13+
dates = {}
14+
for date in *[bd\sub 6 for bd in *birthdates]
15+
dates[date] = (dates[date] or 0) + 1
16+
return true if dates[date] > 1
17+
false
18+
19+
randomBirthdates = (n) ->
20+
[randomBirthdate! for _ = 1, n]
21+
22+
estimatedProbabilityOfSharedBirthday = (n) ->
23+
iterations = 10000
24+
shared = 0
25+
if n > 1
26+
for i = 1, iterations
27+
if sharedBirthday randomBirthdates n
28+
shared += 1
29+
100.0 * shared / iterations
30+
31+
{ :sharedBirthday, :randomBirthdates, :estimatedProbabilityOfSharedBirthday }
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
tablex = require 'pl.tablex'
2+
3+
int_list = (list) -> "{#{table.concat list, ', '}}"
4+
5+
string_list = (list) ->
6+
"{#{table.concat [quote word for word in *list], ', '}}"
7+
8+
{
9+
module_name: 'BafflingBirthdays',
10+
11+
generate_test: (case, level) ->
12+
local lines
13+
switch case.property
14+
when 'sharedBirthday'
15+
lines = {
16+
"birthdates = #{string_list case.input.birthdates}",
17+
"assert.is.#{case.expected} BafflingBirthdays.#{case.property} birthdates",
18+
}
19+
when 'estimatedProbabilityOfSharedBirthday'
20+
lines = {
21+
"result = BafflingBirthdays.#{case.property} #{case.input.groupSize}",
22+
"assert.is.approx_equal #{case.expected}, result"
23+
}
24+
when 'randomBirthdates'
25+
switch case.description
26+
when "generate requested number of birthdates"
27+
lines = {
28+
"result = true",
29+
"for n = 1, 100",
30+
" birthdates = BafflingBirthdays.randomBirthdates n",
31+
" result = result and (#birthdates == n)",
32+
"assert.is.true result"
33+
}
34+
when "years are not leap years"
35+
lines = {
36+
"result = true",
37+
"for birthdate in *BafflingBirthdays.randomBirthdates(100)",
38+
" year = birthdate\\sub 1, 4"
39+
" result = result and not isLeapYear tonumber(year)",
40+
"assert.is.true result"
41+
}
42+
when "months are random"
43+
lines = {
44+
"months = tablex.new 12, 0",
45+
"for birthdate in *BafflingBirthdays.randomBirthdates(100)",
46+
" month = tonumber birthdate\\sub 6, 7"
47+
" assert.is.true 1 <= month and month <= 12",
48+
" months[month] += 1",
49+
"notSeen = [month for month,count in pairs months when count == 0]",
50+
"assert.is.equal 0, #notSeen"
51+
}
52+
when "days are random"
53+
lines = {
54+
"days = tablex.new 31, 0",
55+
"for birthdate in *BafflingBirthdays.randomBirthdates(300)",
56+
" day = tonumber birthdate\\sub 9, 10"
57+
" assert.is.true 1 <= day and day <= 31",
58+
" days[day] += 1",
59+
"notSeen = [day for day,count in pairs days when count == 0]",
60+
"assert.is.equal 0, #notSeen"
61+
}
62+
table.concat [indent line, level for line in *lines], '\n'
63+
64+
65+
test_helpers: [[
66+
-- ----------------------------------------
67+
-- https://lunarmodules.github.io/Penlight/libraries/pl.tablex.html
68+
tablex = require 'pl.tablex'
69+
70+
--
71+
epsilon = 0.5
72+
is_close_to = (state, arguments) ->
73+
{a, b} = arguments
74+
math.abs(a - b) <= epsilon
75+
76+
say = require 'say'
77+
say\set 'assertion.approx_equal.positive', "Expected %s and %s to be within #{epsilon}"
78+
say\set 'assertion.approx_equal.negative', "Expected %s and %s not to be within #{epsilon}"
79+
assert\register 'assertion', 'approx_equal', is_close_to, 'assertion.approx_equal.positive', 'assertion.approx_equal.negative'
80+
81+
--
82+
isLeapYear = (year) -> year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
83+
-- ----------------------------------------
84+
]]
85+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[716dcc2b-8fe4-4fc9-8c48-cbe70d8e6b67]
13+
description = "shared birthday -> one birthdate"
14+
15+
[f7b3eb26-bcfc-4c1e-a2de-af07afc33f45]
16+
description = "shared birthday -> two birthdates with same year, month, and day"
17+
18+
[7193409a-6e16-4bcb-b4cc-9ffe55f79b25]
19+
description = "shared birthday -> two birthdates with same year and month, but different day"
20+
21+
[d04db648-121b-4b72-93e8-d7d2dced4495]
22+
description = "shared birthday -> two birthdates with same month and day, but different year"
23+
24+
[3c8bd0f0-14c6-4d4c-975a-4c636bfdc233]
25+
description = "shared birthday -> two birthdates with same year, but different month and day"
26+
27+
[df5daba6-0879-4480-883c-e855c99cdaa3]
28+
description = "shared birthday -> two birthdates with different year, month, and day"
29+
30+
[0c17b220-cbb9-4bd7-872f-373044c7b406]
31+
description = "shared birthday -> multiple birthdates without shared birthday"
32+
33+
[966d6b0b-5c0a-4b8c-bc2d-64939ada49f8]
34+
description = "shared birthday -> multiple birthdates with one shared birthday"
35+
36+
[b7937d28-403b-4500-acce-4d9fe3a9620d]
37+
description = "shared birthday -> multiple birthdates with more than one shared birthday"
38+
39+
[70b38cea-d234-4697-b146-7d130cd4ee12]
40+
description = "random birthdates -> generate requested number of birthdates"
41+
42+
[d9d5b7d3-5fea-4752-b9c1-3fcd176d1b03]
43+
description = "random birthdates -> years are not leap years"
44+
45+
[d1074327-f68c-4c8a-b0ff-e3730d0f0521]
46+
description = "random birthdates -> months are random"
47+
48+
[7df706b3-c3f5-471d-9563-23a4d0577940]
49+
description = "random birthdates -> days are random"
50+
51+
[89a462a4-4265-4912-9506-fb027913f221]
52+
description = "estimated probability of at least one shared birthday -> for one person"
53+
54+
[ec31c787-0ebb-4548-970c-5dcb4eadfb5f]
55+
description = "estimated probability of at least one shared birthday -> among ten people"
56+
57+
[b548afac-a451-46a3-9bb0-cb1f60c48e2f]
58+
description = "estimated probability of at least one shared birthday -> among twenty-three people"
59+
60+
[e43e6b9d-d77b-4f6c-a960-0fc0129a0bc5]
61+
description = "estimated probability of at least one shared birthday -> among seventy people"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
-- is there at least one shared birthday among the given birthdates?
3+
sharedBirthday: (birthdates) ->
4+
error 'Implement me'
5+
6+
-- generate a list of n random birthdates
7+
randomBirthdates: (n) ->
8+
error 'Implement me'
9+
10+
-- determine the probability that there is a shared birthday amongst n people
11+
estimatedProbabilityOfSharedBirthday: (n) ->
12+
error 'Implement me'
13+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
BafflingBirthdays = require 'baffling_birthdays'
2+
3+
describe 'baffling-birthdays', ->
4+
-- ----------------------------------------
5+
-- https://lunarmodules.github.io/Penlight/libraries/pl.tablex.html
6+
tablex = require 'pl.tablex'
7+
8+
--
9+
epsilon = 0.5
10+
is_close_to = (state, arguments) ->
11+
{a, b} = arguments
12+
math.abs(a - b) <= epsilon
13+
14+
say = require 'say'
15+
say\set 'assertion.approx_equal.positive', "Expected %s and %s to be within #{epsilon}"
16+
say\set 'assertion.approx_equal.negative', "Expected %s and %s not to be within #{epsilon}"
17+
assert\register 'assertion', 'approx_equal', is_close_to, 'assertion.approx_equal.positive', 'assertion.approx_equal.negative'
18+
19+
--
20+
isLeapYear = (year) -> year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
21+
-- ----------------------------------------
22+
23+
describe 'shared birthday', ->
24+
it 'one birthdate', ->
25+
birthdates = {'2000-01-01'}
26+
assert.is.false BafflingBirthdays.sharedBirthday birthdates
27+
28+
pending 'two birthdates with same year, month, and day', ->
29+
birthdates = {'2000-01-01', '2000-01-01'}
30+
assert.is.true BafflingBirthdays.sharedBirthday birthdates
31+
32+
pending 'two birthdates with same year and month, but different day', ->
33+
birthdates = {'2012-05-09', '2012-05-17'}
34+
assert.is.false BafflingBirthdays.sharedBirthday birthdates
35+
36+
pending 'two birthdates with same month and day, but different year', ->
37+
birthdates = {'1999-10-23', '1988-10-23'}
38+
assert.is.true BafflingBirthdays.sharedBirthday birthdates
39+
40+
pending 'two birthdates with same year, but different month and day', ->
41+
birthdates = {'2007-12-19', '2007-04-27'}
42+
assert.is.false BafflingBirthdays.sharedBirthday birthdates
43+
44+
pending 'two birthdates with different year, month, and day', ->
45+
birthdates = {'1997-08-04', '1963-11-23'}
46+
assert.is.false BafflingBirthdays.sharedBirthday birthdates
47+
48+
pending 'multiple birthdates without shared birthday', ->
49+
birthdates = {'1966-07-29', '1977-02-12', '2001-12-25', '1980-11-10'}
50+
assert.is.false BafflingBirthdays.sharedBirthday birthdates
51+
52+
pending 'multiple birthdates with one shared birthday', ->
53+
birthdates = {'1966-07-29', '1977-02-12', '2001-07-29', '1980-11-10'}
54+
assert.is.true BafflingBirthdays.sharedBirthday birthdates
55+
56+
pending 'multiple birthdates with more than one shared birthday', ->
57+
birthdates = {'1966-07-29', '1977-02-12', '2001-12-25', '1980-07-29', '2019-02-12'}
58+
assert.is.true BafflingBirthdays.sharedBirthday birthdates
59+
60+
describe 'random birthdates', ->
61+
pending 'generate requested number of birthdates', ->
62+
result = true
63+
for n = 1, 100
64+
birthdates = BafflingBirthdays.randomBirthdates n
65+
result = result and (#birthdates == n)
66+
assert.is.true result
67+
68+
pending 'years are not leap years', ->
69+
result = true
70+
for birthdate in *BafflingBirthdays.randomBirthdates(100)
71+
year = birthdate\sub 1, 4
72+
result = result and not isLeapYear tonumber(year)
73+
assert.is.true result
74+
75+
pending 'months are random', ->
76+
months = tablex.new 12, 0
77+
for birthdate in *BafflingBirthdays.randomBirthdates(100)
78+
month = tonumber birthdate\sub 6, 7
79+
assert.is.true 1 <= month and month <= 12
80+
months[month] += 1
81+
notSeen = [month for month,count in pairs months when count == 0]
82+
assert.is.equal 0, #notSeen
83+
84+
pending 'days are random', ->
85+
days = tablex.new 31, 0
86+
for birthdate in *BafflingBirthdays.randomBirthdates(300)
87+
day = tonumber birthdate\sub 9, 10
88+
assert.is.true 1 <= day and day <= 31
89+
days[day] += 1
90+
notSeen = [day for day,count in pairs days when count == 0]
91+
assert.is.equal 0, #notSeen
92+
93+
describe 'estimated probability of at least one shared birthday', ->
94+
pending 'for one person', ->
95+
result = BafflingBirthdays.estimatedProbabilityOfSharedBirthday 1
96+
assert.is.approx_equal 0.0, result
97+
98+
pending 'among ten people', ->
99+
result = BafflingBirthdays.estimatedProbabilityOfSharedBirthday 10
100+
assert.is.approx_equal 11.694818, result
101+
102+
pending 'among twenty-three people', ->
103+
result = BafflingBirthdays.estimatedProbabilityOfSharedBirthday 23
104+
assert.is.approx_equal 50.729723, result
105+
106+
pending 'among seventy people', ->
107+
result = BafflingBirthdays.estimatedProbabilityOfSharedBirthday 70
108+
assert.is.approx_equal 99.915958, result

0 commit comments

Comments
 (0)