diff --git a/bin/auto-sync.txt b/bin/auto-sync.txt index 16e02841..2b448126 100644 --- a/bin/auto-sync.txt +++ b/bin/auto-sync.txt @@ -6,6 +6,7 @@ alphametics anagram armstrong-numbers atbash-cipher +baffling-birthdays bank-account binary-search binary-search-tree diff --git a/config.json b/config.json index 3268f8fc..60f6d73a 100644 --- a/config.json +++ b/config.json @@ -212,6 +212,14 @@ "transforming" ] }, + { + "slug": "baffling-birthdays", + "name": "Baffling Birthdays", + "uuid": "0854014f-e678-4844-9c39-55997fff1d07", + "practices": [], + "prerequisites": [], + "difficulty": 1 + }, { "slug": "beer-song", "name": "Beer Song", diff --git a/exercises/practice/baffling-birthdays/.docs/instructions.md b/exercises/practice/baffling-birthdays/.docs/instructions.md new file mode 100644 index 00000000..a01ec867 --- /dev/null +++ b/exercises/practice/baffling-birthdays/.docs/instructions.md @@ -0,0 +1,23 @@ +# Instructions + +Your task is to estimate the birthday paradox's probabilities. + +To do this, you need to: + +- Generate random birthdates. +- Check if a collection of randomly generated birthdates contains at least two with the same birthday. +- Estimate the probability that at least two people in a group share the same birthday for different group sizes. + +~~~~exercism/note +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. +Two birthdates with the same month and day correspond to the same birthday. +~~~~ + +~~~~exercism/caution +The birthday paradox assumes that: + +- There are 365 possible birthdays (no leap years). +- Each birthday is equally likely (uniform distribution). + +Your implementation must follow these assumptions. +~~~~ diff --git a/exercises/practice/baffling-birthdays/.docs/introduction.md b/exercises/practice/baffling-birthdays/.docs/introduction.md new file mode 100644 index 00000000..97dabd1e --- /dev/null +++ b/exercises/practice/baffling-birthdays/.docs/introduction.md @@ -0,0 +1,25 @@ +# Introduction + +Fresh out of college, you're throwing a huge party to celebrate with friends and family. +Over 70 people have shown up, including your mildly eccentric Uncle Ted. + +In one of his usual antics, he bets you £100 that at least two people in the room share the same birthday. +That sounds ridiculous — there are many more possible birthdays than there are guests, so you confidently accept. + +To your astonishment, after collecting the birthdays of just 32 guests, you've already found two guests that share the same birthday. +Accepting your loss, you hand Uncle Ted his £100, but something feels off. + +The next day, curiosity gets the better of you. +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%. + +Ah. So _that's_ why Uncle Ted was so confident. + +Determined to turn the tables, you start looking up other paradoxes; next time, _you'll_ be the one making the bets. + +~~~~exercism/note +The birthday paradox is a [veridical paradox][veridical-paradox]: even though it feels wrong, it is actually true. + +[veridical-paradox]: https://en.wikipedia.org/wiki/Paradox#Quine's_classification +~~~~ + +[birthday-problem]: https://en.wikipedia.org/wiki/Birthday_problem diff --git a/exercises/practice/baffling-birthdays/.meta/config.json b/exercises/practice/baffling-birthdays/.meta/config.json new file mode 100644 index 00000000..092a993f --- /dev/null +++ b/exercises/practice/baffling-birthdays/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "resu-xuniL" + ], + "files": { + "solution": [ + "BafflingBirthdays.php" + ], + "test": [ + "BafflingBirthdaysTest.php" + ], + "example": [ + ".meta/example.php" + ] + }, + "blurb": "Estimate the birthday paradox's probabilities.", + "source": "Erik Schierboom", + "source_url": "https://github.com/exercism/problem-specifications/pull/2539" +} diff --git a/exercises/practice/baffling-birthdays/.meta/example.php b/exercises/practice/baffling-birthdays/.meta/example.php new file mode 100644 index 00000000..b22136ab --- /dev/null +++ b/exercises/practice/baffling-birthdays/.meta/example.php @@ -0,0 +1,60 @@ +format('Y'); + + for ($i = 0; $i < $number; $i++) { + $randomYear = rand(1900, $actualYear); + + if ($randomYear % 400 == 0 || ($randomYear % 4 == 0 && $randomYear % 100 != 0)) { + $randomYear = $randomYear + 1; + } + + $birthdates[] = (new DateTime()) + ->setDate( + $randomYear, + rand(1, 12), + rand(1, 31), + ) + ->format("Y-m-d") + ; + } + + return $birthdates; + } + + public function estimatedProbabilityOfSharedBirthday(int $groupSize): float + { + $runs = 10000; + $count = 0; + + for ($i = 0; $i <= $runs; $i++) { + $count += $this->sharedBirthday($this->randomBirthdates($groupSize)); + } + + return ($count * 100) / $runs; + } +} diff --git a/exercises/practice/baffling-birthdays/.meta/tests.toml b/exercises/practice/baffling-birthdays/.meta/tests.toml new file mode 100644 index 00000000..c76afb46 --- /dev/null +++ b/exercises/practice/baffling-birthdays/.meta/tests.toml @@ -0,0 +1,61 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[716dcc2b-8fe4-4fc9-8c48-cbe70d8e6b67] +description = "shared birthday -> one birthdate" + +[f7b3eb26-bcfc-4c1e-a2de-af07afc33f45] +description = "shared birthday -> two birthdates with same year, month, and day" + +[7193409a-6e16-4bcb-b4cc-9ffe55f79b25] +description = "shared birthday -> two birthdates with same year and month, but different day" + +[d04db648-121b-4b72-93e8-d7d2dced4495] +description = "shared birthday -> two birthdates with same month and day, but different year" + +[3c8bd0f0-14c6-4d4c-975a-4c636bfdc233] +description = "shared birthday -> two birthdates with same year, but different month and day" + +[df5daba6-0879-4480-883c-e855c99cdaa3] +description = "shared birthday -> two birthdates with different year, month, and day" + +[0c17b220-cbb9-4bd7-872f-373044c7b406] +description = "shared birthday -> multiple birthdates without shared birthday" + +[966d6b0b-5c0a-4b8c-bc2d-64939ada49f8] +description = "shared birthday -> multiple birthdates with one shared birthday" + +[b7937d28-403b-4500-acce-4d9fe3a9620d] +description = "shared birthday -> multiple birthdates with more than one shared birthday" + +[70b38cea-d234-4697-b146-7d130cd4ee12] +description = "random birthdates -> generate requested number of birthdates" + +[d9d5b7d3-5fea-4752-b9c1-3fcd176d1b03] +description = "random birthdates -> years are not leap years" + +[d1074327-f68c-4c8a-b0ff-e3730d0f0521] +description = "random birthdates -> months are random" + +[7df706b3-c3f5-471d-9563-23a4d0577940] +description = "random birthdates -> days are random" + +[89a462a4-4265-4912-9506-fb027913f221] +description = "estimated probability of at least one shared birthday -> for one person" + +[ec31c787-0ebb-4548-970c-5dcb4eadfb5f] +description = "estimated probability of at least one shared birthday -> among ten people" + +[b548afac-a451-46a3-9bb0-cb1f60c48e2f] +description = "estimated probability of at least one shared birthday -> among twenty-three people" + +[e43e6b9d-d77b-4f6c-a960-0fc0129a0bc5] +description = "estimated probability of at least one shared birthday -> among seventy people" diff --git a/exercises/practice/baffling-birthdays/BafflingBirthdays.php b/exercises/practice/baffling-birthdays/BafflingBirthdays.php new file mode 100644 index 00000000..63be18c5 --- /dev/null +++ b/exercises/practice/baffling-birthdays/BafflingBirthdays.php @@ -0,0 +1,43 @@ +. + * + * To disable strict typing, comment out the directive below. + */ + +declare(strict_types=1); + +class BafflingBirthdays +{ + public function sharedBirthday(array $birthdates): bool + { + throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__)); + } + + public function randomBirthdates(): bool + { + throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__)); + } + + public function estimatedProbabilityOfSharedBirthday(int $groupSize): float + { + throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__)); + } +} diff --git a/exercises/practice/baffling-birthdays/BafflingBirthdaysTest.php b/exercises/practice/baffling-birthdays/BafflingBirthdaysTest.php new file mode 100644 index 00000000..95f97bc7 --- /dev/null +++ b/exercises/practice/baffling-birthdays/BafflingBirthdaysTest.php @@ -0,0 +1,255 @@ + one birthdate')] + public function testSharedBirthdayOneBirthdate(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertFalse($birthdays->sharedBirthday(["2000-01-01"])); + } + + /** + * uuid: f7b3eb26-bcfc-4c1e-a2de-af07afc33f45 + */ + #[TestDox('shared birthday -> two birthdates with same year, month, and day')] + public function testSharedBirthdayTwoBirthdatesWithSameYearMonthAndDay(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertTrue($birthdays->sharedBirthday(["2000-01-01", "2000-01-01"])); + } + + /** + * uuid: 7193409a-6e16-4bcb-b4cc-9ffe55f79b25 + */ + #[TestDox('shared birthday -> two birthdates with same year and month, but different day')] + public function testSharedBirthdayTwoBirthdatesWithSameYearAndMonthButDifferentDay(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertFalse($birthdays->sharedBirthday(["2012-05-09", "2012-05-17"])); + } + + /** + * uuid: d04db648-121b-4b72-93e8-d7d2dced4495 + */ + #[TestDox('shared birthday -> two birthdates with same month and day, but different year')] + public function testSharedBirthdayTwoBirthdatesWithSameMonthAndDayButDifferentYear(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertTrue($birthdays->sharedBirthday(["1999-10-23", "1988-10-23"])); + } + + /** + * uuid: 3c8bd0f0-14c6-4d4c-975a-4c636bfdc233 + */ + #[TestDox('shared birthday -> two birthdates with same year, but different month and day')] + public function testSharedBirthdayTwoBirthdatesWithSameYearButDifferentMonthAndDay(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertFalse($birthdays->sharedBirthday(["2007-12-19", "2007-04-27"])); + } + + /** + * uuid: df5daba6-0879-4480-883c-e855c99cdaa3 + */ + #[TestDox('shared birthday -> two birthdates with different year, month, and day')] + public function testSharedBirthdayTwoBirthdatesWithDifferentYearMonthAndDay(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertFalse($birthdays->sharedBirthday(["1997-08-04", "1963-11-23"])); + } + + /** + * uuid: 0c17b220-cbb9-4bd7-872f-373044c7b406 + */ + #[TestDox('shared birthday -> multiple birthdates without shared birthday')] + public function testSharedBirthdayMultipleBirthdatesWithoutSharedBirthday(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertFalse( + $birthdays->sharedBirthday([ + "1966-07-29", + "1977-02-12", + "2001-12-25", + "1980-11-10" + ]) + ); + } + + /** + * uuid: 966d6b0b-5c0a-4b8c-bc2d-64939ada49f8 + */ + #[TestDox('shared birthday -> multiple birthdates with one shared birthday')] + public function testSharedBirthdayMultipleBirthdatesWithOneSharedBirthday(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertTrue( + $birthdays->sharedBirthday([ + "1966-07-29", + "1977-02-12", + "2001-07-29", + "1980-11-10" + ]) + ); + } + + /** + * uuid: b7937d28-403b-4500-acce-4d9fe3a9620d + */ + #[TestDox('shared birthday -> multiple birthdates with more than one shared birthday')] + public function testSharedBirthdayMultipleBirthdatesWithMoreThanOneSharedBirthday(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertTrue( + $birthdays->sharedBirthday([ + "1966-07-29", + "1977-02-12", + "2001-12-25", + "1980-07-29", + "2019-02-12" + ]) + ); + } + + /** + * uuid: 70b38cea-d234-4697-b146-7d130cd4ee12 + */ + #[TestDox('random birthdates -> generate requested number of birthdates')] + public function testRandomBirthdatesGenerateRequestedNumberOfBirthdates(): void + { + $generate = rand(100, 1000); + $birthdays = new BafflingBirthdays(); + + $this->assertEquals($generate, count($birthdays->randomBirthdates($generate))); + } + + /** + * uuid: d9d5b7d3-5fea-4752-b9c1-3fcd176d1b03 + */ + #[TestDox('random birthdates -> years are not leap years')] + public function testRandomBirthdatesYearsAreNotLeapYears(): void + { + $generate = rand(100, 1000); + $birthdays = new BafflingBirthdays(); + $result = true; + + foreach ($birthdays->randomBirthdates($generate) as $birthdate) { + $leapCheck = DateTime::createFromFormat('Y-m-d', $birthdate); + $result = $result && $leapCheck && $leapCheck->format('L') == 0; + } + + $this->assertTrue($result); + } + + /** + * uuid: d1074327-f68c-4c8a-b0ff-e3730d0f0521 + */ + #[TestDox('random birthdates -> months are random')] + public function testRandomBirthdatesMonthsAreRandom(): void + { + $generate = rand(100, 1000); + $birthdays = new BafflingBirthdays(); + $months = []; + $randomCheck = true; + + foreach ($birthdays->randomBirthdates($generate) as $birthdate) { + $checkMonth = substr($birthdate, 5, -3); + + $months[$checkMonth] = ($months[$checkMonth] ?? 0) + 1; + } + + $randomCheck = count($months) === 12 ; + + $this->assertTrue($randomCheck); + } + + /** + * uuid: 7df706b3-c3f5-471d-9563-23a4d0577940 + */ + #[TestDox('random birthdates -> days are random')] + public function testRandomBirthdatesDaysAreRandom(): void + { + $generate = rand(300, 1000); + $birthdays = new BafflingBirthdays(); + $days = []; + $randomCheck = true; + + foreach ($birthdays->randomBirthdates($generate) as $birthdate) { + $checkDay = substr($birthdate, -2); + + $days[$checkDay] = ($days[$checkDay] ?? 0) + 1; + } + + $randomCheck = count($days) === 31 ; + + $this->assertTrue($randomCheck); + } + + /** + * uuid: 89a462a4-4265-4912-9506-fb027913f221 + */ + #[TestDox('estimated probability of at least one shared birthday -> for one person')] + public function testEstimatedProbabilityOfAtLeastOneSharedBirthdayForOnePerson(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertEquals( + 0.0, + $birthdays->estimatedProbabilityOfSharedBirthday(1) + ); + } + + /** + * uuid: ec31c787-0ebb-4548-970c-5dcb4eadfb5f + */ + #[TestDox('estimated probability of at least one shared birthday -> among ten people')] + public function testEstimatedProbabilityOfAtLeastOneSharedBirthdayAmongTenPeople(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertEqualsWithDelta( + 11.694818, + $birthdays->estimatedProbabilityOfSharedBirthday(10), + 1 + ); + } + + /** + * uuid: b548afac-a451-46a3-9bb0-cb1f60c48e2f + */ + #[TestDox('estimated probability of at least one shared birthday -> among twenty-three people')] + public function testEstimatedProbabilityOfAtLeastOneSharedBirthdayAmongTwentyThreePeople(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertEqualsWithDelta( + 50.729723, + $birthdays->estimatedProbabilityOfSharedBirthday(23), + 2 + ); + } + + /** + * uuid: b548afac-a451-46a3-9bb0-cb1f60c48e2f + */ + #[TestDox('estimated probability of at least one shared birthday -> among seventy people')] + public function testEstimatedProbabilityOfAtLeastOneSharedBirthdayAmongSeventyPeople(): void + { + $birthdays = new BafflingBirthdays(); + $this->assertEqualsWithDelta( + 99.915958, + $birthdays->estimatedProbabilityOfSharedBirthday(70), + 1 + ); + } +}