Skip to content

Commit e0a4f4d

Browse files
Add baffling-birthdays exercise (#28)
* Add `baffling-birthdays` exercise * Fix tests
1 parent 9c6eb1d commit e0a4f4d

11 files changed

Lines changed: 632 additions & 2 deletions

File tree

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@
216216
"practices": [],
217217
"prerequisites": [],
218218
"difficulty": 4
219+
},
220+
{
221+
"slug": "baffling-birthdays",
222+
"name": "Baffling Birthdays",
223+
"uuid": "4bb8b322-f565-4140-b656-58397cbe8e11",
224+
"practices": [],
225+
"prerequisites": [],
226+
"difficulty": 5
219227
}
220228
]
221229
},
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+
"erikschierboom"
4+
],
5+
"files": {
6+
"solution": [
7+
"solution.jai"
8+
],
9+
"test": [
10+
"tests.jai"
11+
],
12+
"example": [
13+
".meta/example.jai"
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: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
shared_birthday :: (birthdates: [] Calendar_Time) -> bool {
2+
unique_birthdays: [..] int;
3+
4+
for birthdate: birthdates {
5+
birthday_key := cast(s16)birthdate.month_starting_at_0 * 31 + birthdate.day_of_month_starting_at_0;
6+
is_unique, _ := array_add_if_unique(*unique_birthdays, birthday_key);
7+
if !is_unique return true;
8+
}
9+
10+
return false;
11+
}
12+
13+
random_birthdates :: (count: int) -> [] Calendar_Time {
14+
birthdates : [..] Calendar_Time;
15+
array_reserve(*birthdates, count);
16+
for 1..count array_add(*birthdates, random_birthdate());
17+
return birthdates;
18+
}
19+
20+
random_birthdate :: () -> Calendar_Time {
21+
is_leap_year :: (year: int) -> bool {
22+
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
23+
}
24+
25+
DAYS_IN_MONTH := u64.[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
26+
27+
year := cast(s32) (random_get() % 100 + 1920);
28+
month := cast(s8) (random_get() % 12);
29+
day := cast(s8) (random_get() % DAYS_IN_MONTH[month]);
30+
31+
if is_leap_year(year) year -= 1;
32+
33+
return .{year = year, month_starting_at_0 = month, day_of_month_starting_at_0 = day};
34+
}
35+
36+
estimated_probability_of_shared_birthday :: (group_size: int) -> float {
37+
ITERATION_COUNT :: 1000;
38+
39+
matching_birthday_iterations := 0.0;
40+
41+
for 1..ITERATION_COUNT {
42+
birthdates := random_birthdates(group_size);
43+
if shared_birthday(birthdates) matching_birthday_iterations += 1;
44+
}
45+
46+
return (matching_birthday_iterations / ITERATION_COUNT) * 100;
47+
}
48+
49+
#import "Basic";
50+
#import "Random";
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
to_test_case :: (canonical_case: CanonicalCase) -> Test {
2+
using test: Test;
3+
name = to_test_name(canonical_case.description_path);
4+
skip = canonical_case.index > 0;
5+
6+
if canonical_case.property == {
7+
case "sharedBirthday";
8+
_, birthdates := get_property(canonical_case.input, "birthdates");
9+
10+
lines: [..]string;
11+
array_add(*lines, "birthdates : [..] Calendar_Time;");
12+
13+
for birthdate: birthdates.array {
14+
parts := split(birthdate.str, "-");
15+
year := to_integer(parts[0]);
16+
month := to_integer(parts[1]);
17+
day := to_integer(parts[2]);
18+
array_add(*lines, tprint("array_add(*birthdates, .{year = %, month_starting_at_0 = %, day_of_month_starting_at_0 = %});", to_code(year), to_code(month - 1), to_code(day - 1)));
19+
}
20+
array_add(*lines, tprint("assert_equal(%, shared_birthday(birthdates));", to_code(canonical_case.expected.boolean)));
21+
multiline_body = lines;
22+
case "randomBirthdates";
23+
lines: [..]string;
24+
25+
if canonical_case.expected.type == .STRING && canonical_case.expected.str == "length == groupsize" {
26+
array_add(*lines, "for 1..10 {");
27+
array_add(*lines, tprint(" assert_equal(it, random_birthdates(it).count);"));
28+
array_add(*lines, "}");
29+
} else if get_property(canonical_case.expected, "years", "leapYear") {
30+
array_add(*lines, "is_leap_year :: (year: int) -> bool {");
31+
array_add(*lines, " return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);");
32+
array_add(*lines, "}");
33+
array_add(*lines, "");
34+
array_add(*lines, "for birthdate: random_birthdates(50) {");
35+
array_add(*lines, tprint(" assert_equal(false, is_leap_year(birthdate.year));"));
36+
array_add(*lines, "}");
37+
} else if get_property(canonical_case.expected, "months", "random") {
38+
array_add(*lines, "generated_unique_months: [..]s8;");
39+
array_add(*lines, "for birthdate: random_birthdates(100) {");
40+
array_add(*lines, tprint(" array_add_if_unique(*generated_unique_months, birthdate.month_starting_at_0);"));
41+
array_add(*lines, "}");
42+
array_add(*lines, "");
43+
array_add(*lines, tprint("assert_equal(12, generated_unique_months.count);"));
44+
} else if get_property(canonical_case.expected, "days", "random") {
45+
array_add(*lines, "generated_unique_days: [..]s8;");
46+
array_add(*lines, "for birthdate: random_birthdates(300) {");
47+
array_add(*lines, tprint(" array_add_if_unique(*generated_unique_days, birthdate.day_of_month_starting_at_0);"));
48+
array_add(*lines, "}");
49+
array_add(*lines, "");
50+
array_add(*lines, tprint("assert_equal(31, generated_unique_days.count);"));
51+
} else {
52+
assert(false, "Unexpected randomBirthdates test case");
53+
}
54+
55+
multiline_body = lines;
56+
case "estimatedProbabilityOfSharedBirthday";
57+
_, group_size := get_property(canonical_case.input, "groupSize");
58+
expected := ifx canonical_case.expected.number then to_code(canonical_case.expected.number) else "0.0";
59+
60+
lines: [..]string;
61+
array_add(*lines, tprint("estimated_probability := estimated_probability_of_shared_birthday(%);", to_code(group_size)));
62+
array_add(*lines, tprint("expected_probability := %;", expected));
63+
array_add(*lines, tprint("assert_equal(true, estimated_probability > expected_probability - 1.0);"));
64+
array_add(*lines, tprint("assert_equal(true, estimated_probability < expected_probability + 1.0);"));
65+
66+
multiline_body = lines;
67+
}
68+
69+
return test;
70+
}
71+
72+
#run run_generator("baffling-birthdays", to_test_case);
73+
74+
#import "Basic";
75+
#import "String";
76+
#import,dir "../../../shared/generator";
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"

0 commit comments

Comments
 (0)