Skip to content

Commit cd074bc

Browse files
authored
[Practice exercise] Baffling Birthdays (#496)
* [Practice exercise] Baffling Birthdays * changed library imports
1 parent cf6037d commit cd074bc

9 files changed

Lines changed: 331 additions & 0 deletions

File tree

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,14 @@
10631063
"prerequisites": [],
10641064
"difficulty": 6
10651065
},
1066+
{
1067+
"slug": "baffling-birthdays",
1068+
"name": "Baffling Birthdays",
1069+
"uuid": "fd94d735-3d0e-4c6d-8b65-5fe170e00f1e",
1070+
"practices": [],
1071+
"prerequisites": [],
1072+
"difficulty": 6
1073+
},
10661074
{
10671075
"slug": "zebra-puzzle",
10681076
"name": "Zebra Puzzle",
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+
"colinleach"
4+
],
5+
"files": {
6+
"solution": [
7+
"baffling-birthdays.R"
8+
],
9+
"test": [
10+
"test_baffling-birthdays.R"
11+
],
12+
"example": [
13+
".meta/example.R"
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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
library(lubridate)
2+
library(dplyr)
3+
4+
shared_birthday <- function(birthdates) {
5+
birthdays <- birthdates |> str_sub(6)
6+
length(birthdays) > length(unique(birthdays))
7+
}
8+
9+
random_birthdates <- function(groupsize) {
10+
randyear <- sample(1900:year(today()), groupsize, replace = TRUE)
11+
no_leaps <- if_else(leap_year(randyear), randyear - 1, randyear)
12+
make_date(year = no_leaps) + days(sample(0:364, groupsize, replace = TRUE))
13+
}
14+
15+
estimate_probability_of_shared_birthday <- function(groupsize) {
16+
reps <- 500
17+
runs <- replicate(reps, shared_birthday(random_birthdates(groupsize)))
18+
sum(runs) / reps
19+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{%- import "generator_macros.j2" as macros with context -%}
2+
{{ macros.canonical_ref() }}
3+
4+
{{ macros.header() }}
5+
library(lubridate)
6+
7+
{% for case in cases -%}
8+
# {{ case["description"] }}
9+
{% if case["description"].startswith("shared") -%}
10+
{%- for subcase in case["cases"] -%}
11+
test_that("{{ subcase["description"] }}", {
12+
input <- c{{ subcase["input"]["birthdates"] | list_to_tuple }}
13+
expect_{{ subcase["expected"] | lower }}( shared_birthday(input) )
14+
})
15+
{% endfor %}
16+
17+
{% elif case["description"].startswith("random") %}
18+
{# non-deterministic results unsuitable for templating #}
19+
20+
test_that("random birthdates generate requested number of birthdates", {
21+
expect_all_true( sapply(1:20, \(groupsize) length(random_birthdates(groupsize)) == groupsize) )
22+
})
23+
24+
test_that("random birthdates are not in leap years", {
25+
expect_all_false( leap_year(random_birthdates(100)) )
26+
})
27+
28+
test_that("random birthdates appear random", {
29+
birthdates <- random_birthdates(500)
30+
months <- month(birthdates) |> unique()
31+
days <- day(birthdates) |> unique()
32+
expect_gte( length(months), 10 )
33+
expect_gte( length(days), 28 )
34+
})
35+
36+
{% elif case["description"].startswith("estimated") %}
37+
{% for subcase in case["cases"] %}
38+
test_that("{{ subcase["description"] }}", {
39+
expected <- {{ subcase["expected"] / 100 }}
40+
rtol <- ifelse(expected > 0.1 && expected < 0.9, 0.1, 0.01)
41+
expect_equal(
42+
estimate_probability_of_shared_birthday({{ subcase["input"]["groupSize"] }}),
43+
expected,
44+
tolerance = rtol)
45+
})
46+
{% endfor %}
47+
48+
{% endif %}
49+
50+
{% endfor %}
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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
shared_birthday <- function(birthdates) {
2+
3+
}
4+
5+
random_birthdates <- function(groupsize) {
6+
7+
}
8+
9+
estimate_probability_of_shared_birthday <- function(groupsize) {
10+
11+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# These tests are auto-generated with test data from:
2+
# https://github.com/exercism/problem-specifications/tree/main/exercises/baffling-birthdays/canonical-data.json
3+
# File last updated on 2026-03-26
4+
5+
source("./baffling-birthdays.R")
6+
library(testthat)
7+
library(lubridate)
8+
9+
# shared birthday
10+
test_that("one birthdate", {
11+
input <- c('2000-01-01')
12+
expect_false(shared_birthday(input))
13+
})
14+
test_that("two birthdates with same year, month, and day", {
15+
input <- c('2000-01-01', '2000-01-01')
16+
expect_true(shared_birthday(input))
17+
})
18+
test_that("two birthdates with same year and month, but different day", {
19+
input <- c('2012-05-09', '2012-05-17')
20+
expect_false(shared_birthday(input))
21+
})
22+
test_that("two birthdates with same month and day, but different year", {
23+
input <- c('1999-10-23', '1988-10-23')
24+
expect_true(shared_birthday(input))
25+
})
26+
test_that("two birthdates with same year, but different month and day", {
27+
input <- c('2007-12-19', '2007-04-27')
28+
expect_false(shared_birthday(input))
29+
})
30+
test_that("two birthdates with different year, month, and day", {
31+
input <- c('1997-08-04', '1963-11-23')
32+
expect_false(shared_birthday(input))
33+
})
34+
test_that("multiple birthdates without shared birthday", {
35+
input <- c('1966-07-29', '1977-02-12', '2001-12-25', '1980-11-10')
36+
expect_false(shared_birthday(input))
37+
})
38+
test_that("multiple birthdates with one shared birthday", {
39+
input <- c('1966-07-29', '1977-02-12', '2001-07-29', '1980-11-10')
40+
expect_true(shared_birthday(input))
41+
})
42+
test_that("multiple birthdates with more than one shared birthday", {
43+
input <- c(
44+
'1966-07-29',
45+
'1977-02-12',
46+
'2001-12-25',
47+
'1980-07-29',
48+
'2019-02-12'
49+
)
50+
expect_true(shared_birthday(input))
51+
})
52+
53+
54+
# random birthdates
55+
56+
test_that("random birthdates generate requested number of birthdates", {
57+
expect_all_true(sapply(1:20, \(groupsize) {
58+
length(random_birthdates(groupsize)) == groupsize
59+
}))
60+
})
61+
62+
test_that("random birthdates are not in leap years", {
63+
expect_all_false(leap_year(random_birthdates(100)))
64+
})
65+
66+
test_that("random birthdates appear random", {
67+
birthdates <- random_birthdates(500)
68+
months <- month(birthdates) |> unique()
69+
days <- day(birthdates) |> unique()
70+
expect_gte(length(months), 10)
71+
expect_gte(length(days), 28)
72+
})
73+
74+
75+
# estimated probability of at least one shared birthday
76+
77+
test_that("for one person", {
78+
expected <- 0.0
79+
rtol <- ifelse(expected > 0.1 && expected < 0.9, 0.1, 0.01)
80+
expect_equal(
81+
estimate_probability_of_shared_birthday(1),
82+
expected,
83+
tolerance = rtol
84+
)
85+
})
86+
87+
test_that("among ten people", {
88+
expected <- 0.11694818
89+
rtol <- ifelse(expected > 0.1 && expected < 0.9, 0.1, 0.01)
90+
expect_equal(
91+
estimate_probability_of_shared_birthday(10),
92+
expected,
93+
tolerance = rtol
94+
)
95+
})
96+
97+
test_that("among twenty-three people", {
98+
expected <- 0.50729723
99+
rtol <- ifelse(expected > 0.1 && expected < 0.9, 0.1, 0.01)
100+
expect_equal(
101+
estimate_probability_of_shared_birthday(23),
102+
expected,
103+
tolerance = rtol
104+
)
105+
})
106+
107+
test_that("among seventy people", {
108+
expected <- 0.99915958
109+
rtol <- ifelse(expected > 0.1 && expected < 0.9, 0.1, 0.01)
110+
expect_equal(
111+
estimate_probability_of_shared_birthday(70),
112+
expected,
113+
tolerance = rtol
114+
)
115+
})

0 commit comments

Comments
 (0)