diff --git a/data/day4/data.txt b/data/day4/data.txt new file mode 100644 index 0000000..1bd0345 --- /dev/null +++ b/data/day4/data.txt @@ -0,0 +1,601 @@ +91,17,64,45,8,13,47,19,52,68,63,76,82,44,28,56,37,2,78,48,32,58,72,53,9,85,77,89,36,22,49,86,51,99,6,92,80,87,7,25,31,66,84,4,98,67,46,61,59,79,0,3,38,27,23,95,20,35,14,30,26,33,42,93,12,57,11,54,50,75,90,41,88,96,40,81,24,94,18,39,70,34,21,55,5,29,71,83,1,60,74,69,10,62,43,73,97,65,15,16 + +83 40 67 98 4 +50 74 31 30 3 +75 64 79 61 5 +12 59 26 25 72 +36 33 18 54 10 + +68 56 28 57 12 +78 66 20 85 51 +35 23 7 99 44 +86 37 8 45 49 +40 77 32 6 88 + +75 15 20 79 8 +81 69 54 33 28 + 9 53 48 95 27 +65 84 40 71 36 +13 31 6 68 29 + +94 6 30 16 74 +91 47 66 31 90 +14 56 45 55 20 +58 70 27 46 73 +77 67 97 51 54 + +60 12 49 80 52 +15 27 85 82 48 +21 76 83 55 54 + 8 5 4 38 47 +73 2 86 44 99 + +64 60 6 38 37 + 3 69 21 24 11 +36 88 16 55 41 +78 7 81 95 91 +27 34 92 39 30 + +38 57 20 68 49 +21 18 69 97 60 +34 92 0 59 62 +10 43 93 87 64 +53 35 94 76 61 + +48 74 58 13 54 +57 18 37 92 78 +89 10 25 97 43 +38 99 64 6 66 +21 83 29 93 95 + +94 37 98 87 51 +50 65 77 83 95 +68 4 91 53 32 +56 26 15 2 80 +20 55 58 81 33 + +73 32 66 38 89 +18 79 40 78 55 +26 63 93 60 98 +42 65 96 47 57 +45 75 72 23 35 + +64 28 21 80 27 +93 58 71 67 11 +61 20 74 13 90 +76 35 46 94 40 +92 2 4 85 69 + +22 70 87 31 61 +74 78 58 4 90 +63 28 24 35 84 +59 8 89 88 47 +17 48 80 33 32 + +57 7 30 39 19 + 1 13 41 15 50 +44 72 2 5 70 +34 93 60 80 69 +49 14 25 10 33 + +45 41 77 89 27 +68 99 11 32 95 +15 4 72 98 52 +53 28 14 75 44 +57 9 62 92 69 + + 7 21 2 73 40 +52 60 57 53 65 +63 86 36 82 44 +14 28 39 12 80 +66 64 91 50 51 + +82 5 38 41 95 +70 52 11 21 51 +81 20 0 14 83 +57 36 60 59 42 +77 13 85 32 63 + +91 40 42 3 50 +22 24 81 31 93 + 9 79 82 43 89 + 6 77 76 26 37 +29 8 53 23 4 + + 7 78 32 44 74 +29 3 84 38 79 +58 41 87 88 30 +68 19 72 81 47 +15 63 52 6 26 + +20 41 92 84 25 + 9 4 96 85 66 +49 15 50 89 19 +48 45 82 86 60 +29 18 53 47 16 + +75 39 45 31 73 +91 86 69 94 66 +28 61 17 20 0 +88 21 89 41 37 +35 2 10 18 82 + +80 23 4 73 93 +89 8 20 12 45 +74 99 58 90 67 +50 85 35 88 55 +18 65 42 47 48 + +16 38 65 64 25 +20 74 37 15 82 +23 76 97 48 53 +60 93 85 1 35 +77 10 59 2 58 + +11 9 57 40 46 +35 88 29 52 17 +30 2 7 6 0 +13 63 44 68 59 +83 98 5 50 65 + +82 40 2 14 50 + 7 31 91 19 11 +51 42 56 44 6 +66 74 22 95 64 +63 1 17 86 24 + +18 19 66 63 80 +65 23 74 22 85 + 5 7 37 75 51 +38 58 68 83 32 +40 29 31 15 43 + +37 54 13 77 31 +57 96 28 87 95 +10 11 19 49 45 +12 21 79 56 24 +34 64 84 69 17 + + 6 33 48 61 0 +85 34 7 84 37 +25 46 59 76 82 +18 62 20 44 2 +12 78 60 56 99 + +95 6 1 39 2 +46 34 28 64 22 +48 23 89 56 55 +44 81 82 43 74 +65 31 94 49 91 + +69 42 27 52 54 +79 60 62 83 38 + 5 21 56 48 99 +51 40 15 7 24 +92 10 66 64 88 + +99 18 22 52 81 +21 42 13 71 59 +91 38 68 10 25 +54 19 76 60 24 +41 92 2 3 64 + +76 5 25 55 84 +70 15 89 67 68 +34 86 11 4 6 + 9 23 43 41 52 +58 10 88 38 0 + +83 91 85 81 86 + 5 10 89 6 48 +45 77 2 9 90 +74 8 57 75 67 +73 30 49 96 15 + +66 13 82 89 20 + 5 67 94 64 0 +58 73 4 62 49 +59 28 75 79 44 +54 71 57 33 36 + +23 36 29 80 30 +51 91 77 2 84 +78 90 15 21 75 +28 93 22 55 16 +67 50 58 60 68 + +82 80 37 91 7 +54 81 85 25 24 +33 36 89 30 56 +83 95 99 48 10 + 4 44 1 55 79 + + 9 13 53 20 26 + 7 31 49 84 58 +51 91 90 68 55 +19 38 23 81 33 +34 99 85 37 54 + +44 66 81 78 15 +31 14 48 65 0 +26 10 20 4 41 +77 68 95 34 73 +74 12 36 3 60 + + 6 24 78 58 36 +30 51 75 13 40 +17 1 3 42 59 +64 20 4 18 79 +37 61 84 63 7 + +41 83 1 75 18 +14 56 67 32 22 +69 80 46 84 49 +72 21 9 10 35 + 4 37 28 40 12 + +56 80 47 17 70 +12 22 77 81 11 +61 30 58 60 71 +52 0 25 86 65 +59 28 79 20 26 + +70 75 81 18 67 + 2 85 73 8 17 +74 3 34 92 30 +51 72 84 56 45 +37 90 31 97 78 + + 2 73 71 43 69 + 6 54 89 57 93 +81 0 39 25 90 +79 27 92 29 15 +45 76 87 11 91 + +98 35 51 49 34 +23 12 77 27 82 + 6 89 0 76 46 +81 48 99 45 90 +10 75 17 96 29 + +45 19 82 93 0 +84 24 73 2 98 +94 46 7 48 56 +80 34 5 18 31 +58 33 83 29 55 + +66 81 99 54 63 +21 94 72 77 64 +58 52 85 46 68 + 5 6 78 42 4 +76 38 51 24 33 + +93 26 5 59 67 +13 84 76 4 69 + 0 17 30 83 48 + 8 53 32 14 92 +94 18 66 46 61 + +28 48 38 6 25 +70 39 71 77 22 +66 94 18 43 36 +30 67 57 9 90 +15 34 50 3 86 + +11 90 99 92 87 +78 79 56 21 50 +19 18 22 20 30 +95 41 59 85 26 +66 58 46 38 57 + +49 92 2 93 77 +46 89 44 57 19 +53 8 32 18 88 +54 95 59 70 10 +72 84 86 42 81 + +44 78 25 4 57 +72 7 42 94 8 +61 79 11 29 59 +22 82 6 90 12 +98 77 5 68 50 + +48 41 64 15 57 +76 7 52 53 93 +70 84 94 38 35 +47 18 13 51 21 +77 62 63 3 65 + +31 33 48 79 69 +30 9 83 53 50 +60 94 36 2 28 +59 19 10 5 40 +26 41 72 14 96 + + 0 16 49 75 17 +28 20 21 99 94 +15 8 4 68 71 +23 53 76 19 74 +79 61 72 70 52 + +70 89 12 80 76 +14 18 16 4 91 +34 64 43 51 71 + 6 78 30 5 13 +57 42 15 73 24 + +64 99 72 41 54 +21 29 25 40 9 +92 48 82 70 98 +65 62 8 78 27 +71 86 36 34 23 + +23 19 72 77 63 +85 0 61 40 14 +69 76 18 56 95 +68 66 28 79 13 +83 84 45 89 2 + +18 40 28 70 37 +80 30 67 96 34 +77 25 97 32 11 +48 46 89 14 29 + 2 8 95 0 12 + + 0 26 1 9 30 +17 2 78 18 65 +84 7 61 93 81 +80 44 82 23 99 +72 95 19 60 28 + +37 39 0 20 21 +91 36 93 16 22 +53 95 26 72 25 +97 33 60 55 65 +79 56 73 29 75 + +22 58 99 57 28 + 2 56 93 91 18 +44 64 92 85 46 +70 47 89 27 54 +83 5 48 97 72 + +72 1 73 68 36 +31 8 14 41 35 +23 96 7 92 83 +56 39 77 93 91 +20 28 67 10 11 + +62 27 17 54 0 +35 60 73 20 5 +23 58 46 99 75 +19 53 79 70 88 +31 85 77 1 32 + +22 90 81 42 55 +70 78 86 19 94 + 1 43 15 33 51 +84 96 87 58 6 +49 64 4 59 23 + +82 63 58 75 89 +35 37 52 80 24 +93 50 76 79 1 +86 59 30 92 7 +42 11 55 70 22 + +83 3 71 28 95 +70 23 68 57 1 +60 6 19 63 32 +64 55 97 81 49 +91 80 88 5 35 + +23 68 51 62 20 +70 52 98 34 41 +12 21 85 43 84 +69 49 36 28 0 +76 30 58 91 60 + +30 72 6 41 43 +67 79 46 96 99 +58 71 39 87 69 +17 18 11 57 25 +45 75 16 33 42 + +22 75 24 74 90 +34 70 44 86 23 +29 59 68 4 48 +88 45 92 27 49 +47 77 26 99 82 + +42 29 21 74 33 +64 37 38 50 84 +46 44 41 1 67 +53 66 96 68 59 + 6 94 11 31 99 + +24 32 71 87 57 +42 26 55 80 99 +82 27 16 19 92 +96 48 62 31 61 +60 89 95 18 6 + +99 33 55 71 29 +75 37 23 27 98 + 2 78 90 18 35 +59 10 56 0 6 +12 19 76 70 96 + +33 37 23 61 80 + 6 13 68 51 76 +92 25 3 95 55 +99 63 17 52 30 +11 94 42 5 98 + +77 37 25 14 73 +95 90 10 19 72 +78 30 44 47 91 + 3 60 32 5 66 +21 55 87 98 6 + + 6 60 82 90 98 +21 70 54 66 27 +37 64 55 10 14 +57 25 84 50 20 +42 59 85 3 73 + +74 84 92 10 51 +57 82 93 90 44 +41 43 76 48 59 +79 49 69 16 72 +37 29 63 15 68 + +37 90 97 86 18 + 2 83 30 53 92 +45 35 78 47 40 +67 61 17 14 84 +32 33 81 10 11 + +46 48 39 3 50 +83 29 91 73 67 +25 43 89 71 36 +63 62 78 95 18 +82 34 23 85 11 + +19 68 80 50 13 + 1 45 51 27 39 +98 26 24 46 49 +14 92 63 88 66 +15 44 84 47 94 + +19 39 93 43 86 +91 58 3 69 41 +18 36 95 52 83 +12 6 22 48 0 +25 70 40 88 73 + +95 11 94 13 14 +64 87 57 98 49 +47 88 84 61 2 +46 21 15 74 59 +82 73 78 3 51 + +18 72 29 7 36 +96 67 81 78 23 +43 40 44 47 98 +41 26 15 90 71 +42 62 93 70 2 + +17 8 59 25 33 +81 47 55 99 48 +86 14 71 54 50 +90 11 23 18 0 +97 65 82 68 42 + +50 54 68 90 83 +10 28 77 55 61 +38 60 52 80 44 +40 81 14 24 87 +51 82 42 30 8 + +54 5 64 22 60 +70 19 83 11 45 +46 39 2 56 6 +61 8 28 20 94 + 0 4 81 34 84 + +96 21 48 89 15 +91 40 9 97 65 +26 58 10 18 78 +98 79 29 80 28 +17 59 43 84 99 + +67 73 21 9 31 +68 37 26 65 84 +63 24 42 27 40 +61 25 30 34 35 +53 23 48 81 29 + +24 34 5 67 62 +89 85 68 37 78 +42 87 13 49 41 +74 55 70 86 76 +73 94 97 63 48 + +88 24 6 75 30 +77 64 16 34 93 +36 76 0 40 81 +67 14 89 84 95 +32 19 18 66 9 + +97 71 65 30 69 +41 21 40 31 33 +50 55 35 52 53 + 4 51 13 81 72 +12 83 14 64 18 + +97 7 8 74 10 + 3 92 31 25 41 +20 32 45 72 55 + 1 43 49 98 27 +99 54 57 13 76 + +86 81 67 6 97 +34 18 96 43 56 +59 75 17 26 9 + 0 38 60 94 14 + 4 55 64 61 88 + +37 15 48 43 66 +45 54 90 81 47 +63 64 28 82 93 +34 52 6 99 61 +49 12 71 23 46 + +90 87 89 97 1 +48 0 82 60 43 +55 30 68 25 83 +78 3 23 16 66 +98 2 19 63 17 + +89 52 49 14 38 +69 12 50 17 90 +58 53 26 20 29 +39 65 43 7 5 +84 68 94 85 25 + +95 25 42 36 47 +50 54 83 84 37 +94 70 99 79 18 +57 8 69 52 31 +66 20 35 71 38 + +81 18 47 68 15 + 3 50 16 83 37 +34 31 9 57 76 +74 95 40 63 48 +13 28 20 43 66 + +52 21 62 41 67 +22 56 36 18 23 +59 44 27 73 3 +72 50 19 33 76 +45 55 70 46 92 + +72 96 50 83 68 +31 78 59 57 93 +43 58 17 52 35 +87 34 91 76 0 +54 75 53 25 62 + +21 53 68 5 80 +47 67 6 81 9 +64 46 35 26 39 +50 24 84 45 71 +66 15 83 3 97 + +22 97 31 90 63 +21 51 38 74 78 +10 64 92 82 1 +70 12 75 16 14 +68 50 35 73 26 diff --git a/data/day4/example.txt b/data/day4/example.txt new file mode 100644 index 0000000..669a51d --- /dev/null +++ b/data/day4/example.txt @@ -0,0 +1,19 @@ +7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1 + +22 13 17 11 0 + 8 2 23 4 24 +21 9 14 16 7 + 6 10 3 18 5 + 1 12 20 15 19 + + 3 15 0 2 22 + 9 18 13 17 5 +19 8 7 25 23 +20 11 10 24 4 +14 21 16 12 6 + +14 21 17 24 4 +10 16 15 9 19 +18 8 23 26 20 +22 11 13 6 5 + 2 0 12 3 7 diff --git a/data/day4/puzzle.md b/data/day4/puzzle.md new file mode 100644 index 0000000..7163395 --- /dev/null +++ b/data/day4/puzzle.md @@ -0,0 +1,79 @@ +# Day 4: Giant Squid + +[https://adventofcode.com/2021/day/4](https://adventofcode.com/2021/day/4) + +## Description + +### Part One + +You're already almost 1.5km (almost a mile) below the surface of the ocean, already so deep that you can't see any sunlight. What you _can_ see, however, is a giant squid that has attached itself to the outside of your submarine. + +Maybe it wants to play [bingo](https://en.wikipedia.org/wiki/Bingo_(American_version))? + +Bingo is played on a set of boards each consisting of a 5x5 grid of numbers. Numbers are chosen at random, and the chosen number is _marked_ on all boards on which it appears. (Numbers may not appear on all boards.) If all numbers in any row or any column of a board are marked, that board _wins_. (Diagonals don't count.) + +The submarine has a _bingo subsystem_ to help passengers (currently, you and the giant squid) pass the time. It automatically generates a random order in which to draw numbers and a random set of boards (your puzzle input). For example: + + 7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1 + + 22 13 17 11 0 + 8 2 23 4 24 + 21 9 14 16 7 + 6 10 3 18 5 + 1 12 20 15 19 + + 3 15 0 2 22 + 9 18 13 17 5 + 19 8 7 25 23 + 20 11 10 24 4 + 14 21 16 12 6 + + 14 21 17 24 4 + 10 16 15 9 19 + 18 8 23 26 20 + 22 11 13 6 5 + 2 0 12 3 7 + + +After the first five numbers are drawn (`7`, `4`, `9`, `5`, and `11`), there are no winners, but the boards are marked as follows (shown here adjacent to each other to save space): + + 22 13 17 11 0 3 15 0 2 22 14 21 17 24 4 + 8 2 23 4 24 9 18 13 17 5 10 16 15 9 19 + 21 9 14 16 7 19 8 7 25 23 18 8 23 26 20 + 6 10 3 18 5 20 11 10 24 4 22 11 13 6 5 + 1 12 20 15 19 14 21 16 12 6 2 0 12 3 7 + + +After the next six numbers are drawn (`17`, `23`, `2`, `0`, `14`, and `21`), there are still no winners: + + 22 13 17 11 0 3 15 0 2 22 14 21 17 24 4 + 8 2 23 4 24 9 18 13 17 5 10 16 15 9 19 + 21 9 14 16 7 19 8 7 25 23 18 8 23 26 20 + 6 10 3 18 5 20 11 10 24 4 22 11 13 6 5 + 1 12 20 15 19 14 21 16 12 6 2 0 12 3 7 + + +Finally, `24` is drawn: + + 22 13 17 11 0 3 15 0 2 22 14 21 17 24 4 + 8 2 23 4 24 9 18 13 17 5 10 16 15 9 19 + 21 9 14 16 7 19 8 7 25 23 18 8 23 26 20 + 6 10 3 18 5 20 11 10 24 4 22 11 13 6 5 + 1 12 20 15 19 14 21 16 12 6 2 0 12 3 7 + + +At this point, the third board _wins_ because it has at least one complete row or column of marked numbers (in this case, the entire top row is marked: _`14 21 17 24 4`_). + +The _score_ of the winning board can now be calculated. Start by finding the _sum of all unmarked numbers_ on that board; in this case, the sum is `188`. Then, multiply that sum by _the number that was just called_ when the board won, `24`, to get the final score, `188 * 24 = 4512`. + +To guarantee victory against the giant squid, figure out which board will win first. _What will your final score be if you choose that board?_ + +### Part Two + +On the other hand, it might be wise to try a different strategy: let the giant squid win. + +You aren't sure how many bingo boards a giant squid could play at once, so rather than waste time counting its arms, the safe thing to do is to _figure out which board will win last_ and choose that one. That way, no matter which boards it picks, it will win for sure. + +In the above example, the second board is the last to win, which happens after `13` is eventually called and its middle column is completely marked. If you were to keep playing until this point, the second board would have a sum of unmarked numbers equal to `148` for a final score of `148 * 13 = 1924`. + +Figure out which board will win last. _Once it wins, what would its final score be?_ diff --git a/src/Day4.php b/src/Day4.php new file mode 100644 index 0000000..aa2c38d --- /dev/null +++ b/src/Day4.php @@ -0,0 +1,195 @@ +|string> $winningCard + * + * @return int + */ + protected function calculateScore(array $winningCard, int $number): int + { + $return = []; + array_walk_recursive($winningCard, static function (bool $value, int $key) use (&$return) { + $return[$key] = $value; + }); + $unusedNumbers = array_keys(array_filter($return, static fn (bool $value) => !$value)); + + return (int) array_sum($unusedNumbers) * $number; + } + + /** + * {@inheritdoc} + */ + protected function part1(array $data): int|string + { + return $this->playBingo($data, firstWins: true); + } + + /** + * {@inheritdoc} + */ + protected function part2(array $data): int|string + { + return $this->playBingo($data, firstWins: false); + } + + /** + * @param string $numberList + * @param string $separator + * + * @return int[] + * + * @psalm-return array + */ + protected function explodeNumbers(string $numberList, string $separator): array + { + return array_map( + static fn ($value) => (int) $value, + array_filter( + explode($separator, $numberList), + static fn (string $value) => $value !== '' + ) + ); + } + + /** + * @param string[] $data + * @param bool $firstWins + * + * @return int|string + */ + protected function playBingo(array $data, bool $firstWins = true): int|string + { + $numbers = $this->explodeNumbers(array_shift($data), ','); + $cards = $this->setupCards($data); + $finishedCards = []; + + // Call the numbers. + foreach ($numbers as $number) { + /** + * @var int $cardIndex + * @var int[] $cardRows + */ + foreach ($cards as $cardIndex => $cardRows) { + if (isset($finishedCards[$cardIndex])) { + continue; + } + + /** + * @var int $cardRowIndex + * @var int[] $cardRow + */ + foreach ($cardRows as $cardRowIndex => $cardRow) { + if (isset($cardRow[$number])) { + $cards[$cardIndex][$cardRowIndex][$number] = true; + } + } + } + + $winningCards = $this->checkCards($cards, $finishedCards); + if (empty($winningCards)) { + continue; + } + + foreach ($winningCards as $winningCard) { + $lastWinningCard = $cards[$winningCard]; + $lastWinNumber = $number; + $finishedCards[$winningCard] = true; + + if ($firstWins) { + return $this->calculateScore($lastWinningCard, $number); + } + } + } + + if (isset($lastWinningCard, $lastWinNumber)) { + return $this->calculateScore($lastWinningCard, $lastWinNumber); + } + + return 'Computer says no...'; + } + + /** + * @param string[] $data + * + * @return ((false|int)[]|string)[][] + * + * @psalm-return array|string>> + */ + protected function setupCards(array $data): array + { + $cards = array_chunk($data, 5); + foreach ($cards as $card => $rows) { + $cards[$card] = array_map(fn ($value) => $this->explodeNumbers($value, ' '), $rows); + + foreach ($cards[$card] as $row => $number) { + $cards[$card][$row] = array_fill_keys(array_values($number), false); + } + } + + return $cards; + } + + /** + * @return int[] + * + * @psalm-return list + */ + protected function checkCards(array $cards, array $finishedCards): array + { + $winningCards = []; + // Check rows + /** + * @var int $cardIndex + * @var int[] $rows + */ + foreach ($cards as $cardIndex => $rows) { + if (isset($finishedCards[$cardIndex])) { + continue; + } + + /** @var int[] $row */ + foreach ($rows as $row) { + if ($this->arrayHasSingleValue($row, true)) { + $winningCards[] = $cardIndex; + } + } + + // Get the vertical numbers. + $colValues = []; + for ($rowIndex = 0; $rowIndex < 5; ++$rowIndex) { + for ($colIndex = 0; $colIndex < 5; ++$colIndex) { + if (!isset($colValues[$colIndex])) { + $colValues[$colIndex] = []; + } + + $colValues[$colIndex] += array_slice((array) $rows[$rowIndex], $colIndex, 1, preserve_keys: true); + } + } + + // See if there's a bingo on the vertical numbers. + foreach ($colValues as $colIndex => $colValue) { + if ($this->arrayHasSingleValue($colValue, true)) { + $winningCards[] = $cardIndex; + } + } + } + + return $winningCards; + } + + protected function arrayHasSingleValue(array $array, bool $value): bool + { + return count(array_unique($array)) === 1 && end($array) === $value; + } +} diff --git a/tests/Day4Test.php b/tests/Day4Test.php new file mode 100644 index 0000000..4ed3515 --- /dev/null +++ b/tests/Day4Test.php @@ -0,0 +1,24 @@ +