From b99d6427760da9c7a95905b78090a1acdaf35b6a Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Thu, 9 Dec 2021 23:27:45 -0800 Subject: [PATCH] Add files via upload --- ipynb/Advent-2021.ipynb | 636 +++++++++++++++++++++++++++++++++------- 1 file changed, 534 insertions(+), 102 deletions(-) diff --git a/ipynb/Advent-2021.ipynb b/ipynb/Advent-2021.ipynb index eed8555..c23845a 100644 --- a/ipynb/Advent-2021.ipynb +++ b/ipynb/Advent-2021.ipynb @@ -4,13 +4,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "
Peter Norvig, 1–25 Dec 2021
\n", + "
Peter Norvig
December 1–25, 2021
\n", "\n", "# Advent of Code 2021\n", "\n", - "I'm doing [Advent of Code 2021](https://adventofcode.com/) (AoC) this year. I won't be competing for points\\, just for fun.\n", + "I'm doing [Advent of Code](https://adventofcode.com/) (AoC) this year. I'm not competing for points\\, just for fun.\n", "\n", - "You'll have to click on each day's link (e.g. [Day 1](https://adventofcode.com/2021/day/1)) to see the full description of each puzzle; I won't repeat them in this notebook. \n", + "To fully understand each puzzle you'll have to click on each day's link (e.g. [Day 1](https://adventofcode.com/2021/day/1)); I'll give only a partial description of the puzzles. \n", "\n", "Part of the idea of AoC is that you have to make some design choices to solve part 1 *before* you get to see the description of part 2. So there is a tension of wanting the solution to part 1 to provide general components that might be re-used in part 2, without falling victim to [YAGNI](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it). In this notebook I won't refactor the code for part 1 based on what I see in part 2 (although I may edit the code for clarity, without changing the initial approach).\n", "\n", @@ -31,20 +31,20 @@ "from typing import *\n", "from statistics import mean, median\n", "\n", + "import matplotlib.pyplot as plt\n", "import functools\n", "import math\n", - "import re\n", - "\n", - "cat = ''.join\n", - "flatten = chain.from_iterable\n", - "cache = functools.lru_cache(None)" + "import re" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Here are two functions that I will use each day, `parse` and `answer`, and two parser functions, `ints` and `atoms`:" + "Each day's work will consist of three tasks:\n", + "- **Input**: Parse the day's input file with the function `parse(day, parser, sep)`, which treats the input as a sequence of *entries*, separated by `sep` (default newline); applies `parser` to each entry; and returns the results as a tuple. (Note: `ints` and `atoms` are useful `parser` functions (as are `int` and `str`).)\n", + "- **Part 1**: Write code to compute the answer to Part 1, and submit the answer to the AoC site. Use the function `answer` to record the correct answer and serve as a regression test when I re-run the notebook.\n", + "- **Part 2**: Repeat coding and `answer` for Part 2.\n" ] }, { @@ -55,8 +55,8 @@ "source": [ "def parse(day, parser=str, sep='\\n') -> tuple:\n", " \"\"\"Split the day's input file into entries separated by `sep`, and apply `parser` to each.\"\"\"\n", - " sections = open(f'AOC2021/input{day}.txt').read().rstrip().split(sep)\n", - " return mapt(parser, sections)\n", + " entries = open(f'AOC2021/input{day}.txt').read().rstrip().split(sep)\n", + " return mapt(parser, entries)\n", "\n", "def answer(puzzle_number, got, expected) -> bool:\n", " \"\"\"Verify the answer we got was expected.\"\"\"\n", @@ -91,7 +91,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "A few additional utility functions:" + "A few additional utility functions that I have used in the past:" ] }, { @@ -119,28 +119,54 @@ " return result\n", "\n", "def total(counts: Counter) -> int: \n", - " \"\"\"The sum of all the counts in a Counter\"\"\"\n", + " \"\"\"The sum of all the counts in a Counter.\"\"\"\n", " return sum(counts.values())\n", "\n", + "Point = Tuple[int, int] # (x, y) points on a grid\n", + "\n", + "class Grid(dict):\n", + " \"\"\"A 2D grid, indexed by (x, y) Points.\"\"\"\n", + " def __init__(self, mapping):\n", + " \"\"\"Initialize with a mapping of {(0, 0): val0, (0, 1): val1, ...}\"\"\"\n", + " self.update(mapping)\n", + " self.width = max(x for x, y in self)\n", + " self.height = max(y for x, y in self)\n", + " \n", + " def neighbors(self, point):\n", + " \"\"\"The 4 orthogonal neighbors of a point, not going over edge.\"\"\"\n", + " x, y = point\n", + " return [p for p in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)) \n", + " if p in self]\n", + " \n", + " def neighbors8(self, point):\n", + " \"\"\"The 8 orthogonal and diagonal neighbors of a point, not going over edge.\"\"\"\n", + " x, y = point\n", + " return [p for p in ((x + 1, y + 1), (x + 1, y - 1), (x - 1, y + 1), (x - 1, y - 1))\n", + " if p in self] + self.neighbors(point)\n", + "\n", "def sign(x) -> int: return (0 if x == 0 else +1 if x > 0 else -1)\n", " \n", - "def dotproduct(A, B) -> float: return sum(a * b for a, b in zip(A, B))" + "def dotproduct(A, B) -> float: return sum(a * b for a, b in zip(A, B))\n", + "\n", + "def transpose(matrix) -> list: return list(zip(*matrix))\n", + "\n", + "cat = ''.join\n", + "flatten = chain.from_iterable\n", + "cache = functools.lru_cache(None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# [Day 1](https://adventofcode.com/2021/day/1): Sonar Sweep\n", - "\n", - "This year's theme involves Santa's Elves on a submarine. [Gary J Grady](https://twitter.com/GaryJGrady/) has some nice drawings to set the scene:\n", + "This year's AoC theme involves Santa's Elves on a submarine. [Gary J Grady](https://twitter.com/GaryJGrady/) has some nice drawings to set the scene:\n", "\n", "\n", "\n", - "Each entry in the input is an integer depth measurement, such as \"`148`\".\n", + "# [Day 1](https://adventofcode.com/2021/day/1): Sonar Sweep\n", "\n", - "1. How many measurements are larger than the previous measurement?\n", - "2. Consider sums of a three-measurement sliding window. How many sums are larger than the previous sum?" + "\n", + "- **Input**: Each entry in the input is an integer depth measurement, such as \"`148`\".\n" ] }, { @@ -152,6 +178,13 @@ "in1 = parse(1, int)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 1**: How many measurements are larger than the previous measurement?" + ] + }, { "cell_type": "code", "execution_count": 5, @@ -177,6 +210,13 @@ "answer(1.1, increases(in1), 1400)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 2**: Consider sums of a three-measurement sliding window. How many sums are larger than the previous sum?" + ] + }, { "cell_type": "code", "execution_count": 6, @@ -202,32 +242,15 @@ "answer(1.2, window_increases(in1), 1429)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Note: Daily Code Pattern\n", - "\n", - "Each day will follow a pattern similar to the code above for Day 1:\n", - "\n", - "1. I'll first parse the input file, in this case with `in1 = parse(1, int)`. The \"`1`\" means to read the file for day 1; `int` means each entry of the input file is parsed as an `int`. The resulting integers will be returned as a tuple. By default, each entry is a single line of input; the `sep` keyword for `parse` can be used for input files in which that is not true.\n", - "2. I'll write a function to solve the problem. In this case, the function call is `increases(in1)`.\n", - "3. I'll then submit the answer to AoC and verify it is correct. \n", - "4. If it is correct, I'll moe on to part 2. \n", - "5. When both parts are correct, I'll use the function `answers` to record the correct answer, and assert that the computation matches the answer. \n", - "6. For more complex puzzles, I will include some `assert` statements to show that I am getting the right partial results on the small example given in the puzzle description." - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ "# [Day 2](https://adventofcode.com/2021/day/2): Dive! \n", "\n", - "Each entry in the input is a command, like \"`forward 1`\", \"`down 2`\", or \"`up 3`\".\n", + "- **Input**: Each entry in the input is a command, like \"`forward 1`\", \"`down 2`\", or \"`up 3`\".\n", "\n", - "1. Calculate the horizontal position and depth you would have after following the planned course. What do you get if you multiply your final horizontal position by your final depth?\n", - "1. Using the new interpretation of the commands, calculate the horizontal position and depth you would have after following the planned course. What do you get if you multiply your final horizontal position by your final depth? (New interpretation: the \"down\" and \"up\" commands no longer change depth, rather they change *aim*, and going forward *n* units both increments position by *n* and depth by *aim* × *n*.)" + "I'll parse a command into a tuple like `('forward', 1)`." ] }, { @@ -239,6 +262,13 @@ "in2 = parse(2, atoms)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 1**: Calculate the horizontal position and depth you would have after following the planned course. What do you get if you multiply your final horizontal position by your final depth?" + ] + }, { "cell_type": "code", "execution_count": 8, @@ -268,6 +298,15 @@ "answer(2.1, drive(in2), 1670340)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 2**: Using the new interpretation of the commands, calculate the horizontal position and depth you would have after following the planned course. What do you get if you multiply your final horizontal position by your final depth? \n", + "\n", + "The new interpretation is that the \"down\" and \"up\" commands no longer change depth, rather they change *aim*, and going forward *n* units both increments position by *n* and depth by *aim* × *n*." + ] + }, { "cell_type": "code", "execution_count": 9, @@ -304,10 +343,9 @@ "source": [ "# [Day 3](https://adventofcode.com/2021/day/3): Binary Diagnostic\n", "\n", - "Each entry in the input is a bit string, such as \"`101000111100`\".\n", + "- **Input**: Each entry in the input is a bit string, such as \"`101000111100`\".\n", "\n", - "1. What is the power consumption of the submarine (product of gamma and epsilon rates)?\n", - "2. What is the life support rating of the submarine (product of oxygen and CO2)?" + "I'll parse them as strings; I won't convert them into ints." ] }, { @@ -316,7 +354,14 @@ "metadata": {}, "outputs": [], "source": [ - "in3 = parse(3, str) # Parse into bit strings, (e.g. '1110'), not binary ints (e.g. 14)" + "in3 = parse(3, str)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 1**: What is the power consumption of the submarine (product of gamma and epsilon rates)?" ] }, { @@ -360,6 +405,13 @@ "answer(3.1, power(in3), 2261546)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 2**: What is the life support rating of the submarine (product of oxygen and CO2)?" + ] + }, { "cell_type": "code", "execution_count": 12, @@ -400,14 +452,9 @@ "source": [ "# [Day 4](https://adventofcode.com/2021/day/4): Giant Squid\n", "\n", - "The first entry of the input is a permutation of the integers 0-99. Subsequent entries are bingo boards: 5 lines of 5 ints each. Entries are separated by *two* newlines. Bingo games will be played against a giant squid.\n", + "- **Input**: The first entry of the input is a permutation of the integers 0-99. Subsequent entries are bingo boards: 5 lines of 5 ints each. Entries are separated by *two* newlines. (Bingo games will be played against a giant squid.)\n", "\n", - "1. What will your final score be if you choose the first bingo board to win?\n", - "2. Figure out which board will win last. Once it wins, what would its final score be?\n", - "\n", - "I'll represent a board as a tuple of 25 ints; that makes `parse` easy: the permutation of integers and the bingo boards can both be parsed by `ints`. \n", - "\n", - "I'm worried about an ambiguity: what if two boards win at the same time? I'll have to assume Eric arranged it so that can't happen. I'll define `bingo_winners` to return a list of boards that win when a number has just been called, and I'll arbitrarily choose the first of them." + "I'll represent a board as a tuple of 25 ints; that makes `parse` easy: the permutation of integers and the bingo boards can both be parsed by `ints`. " ] }, { @@ -419,6 +466,20 @@ "order, *boards = in4 = parse(4, ints, sep='\\n\\n')" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 1**: What will your final score be if you choose the first bingo board to win?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "I'm worried about an ambiguity: what if two boards win at the same time? I'll have to assume Eric arranged it so that can't happen. I'll define `bingo_winners` to return a list of boards that win when a number has just been called, and I'll arbitrarily choose the first of them." + ] + }, { "cell_type": "code", "execution_count": 14, @@ -470,6 +531,13 @@ "answer(4.1, bingo(boards, order), 39902)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 2**: Figure out which board will win last. Once it wins, what would its final score be?" + ] + }, { "cell_type": "code", "execution_count": 15, @@ -514,10 +582,7 @@ "source": [ "# [Day 5](https://adventofcode.com/2021/day/5): Hydrothermal Venture\n", "\n", - "Each entry in the input is a \"line\" denoted by start and end x,y points, e.g. \"`0,9 -> 5,9`\". I'll represent a line as a 4-tuple of integers, e.g. `(0, 9, 5, 9)`.\n", - "\n", - "1. Consider only horizontal and vertical lines. At how many points do at least two lines overlap?\n", - "2. Consider all of the lines (including diagonals, which are all at ±45°). At how many points do at least two lines overlap?" + "- **Input**: Each entry in the input is a \"line\" denoted by start and end x,y points, e.g. \"`0,9 -> 5,9`\". I'll represent a line as a 4-tuple of integers, e.g. `(0, 9, 5, 9)`." ] }, { @@ -529,6 +594,13 @@ "in5 = parse(5, ints)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 1**: Consider only horizontal and vertical lines. At how many points do at least two lines overlap?" + ] + }, { "cell_type": "code", "execution_count": 17, @@ -568,6 +640,13 @@ "answer(5.1, overlaps(in5), 7436)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 2**: Consider all of the lines (including diagonals, which are all at ±45°). At how many points do at least two lines overlap?" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -612,7 +691,7 @@ "assert points((1, 1, 3, 3), True) == [(1, 1), (2, 2), (3, 3)]\n", "assert points((9, 7, 7, 9), True) == [(9, 7), (8, 8), (7, 9)]\n", "\n", - "answer(5.1, overlaps(in5), 7436)\n", + "answer(5.1, overlaps(in5, False), 7436)\n", "answer(5.2, overlaps(in5, True), 21104)" ] }, @@ -622,10 +701,7 @@ "source": [ "# [Day 6](https://adventofcode.com/2021/day/6): Lanternfish\n", "\n", - "The input is a single line of comma-separated integers, each one the age of a lanternfish. Over time, the lanternfish age and reproduce in a specified way.\n", - "\n", - "1. Find a way to simulate lanternfish. How many lanternfish would there be after 80 days?\n", - "2. How many lanternfish would there be after 256 days?" + "- **Input**: The input is a single line of comma-separated integers, each one the age of a lanternfish. Over time, the lanternfish age and reproduce in a specified way." ] }, { @@ -641,6 +717,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "- **Part 1**: Find a way to simulate lanternfish. How many lanternfish would there be after 80 days?\n", + "\n", "Although the puzzle description treats each fish individually, I won't take the bait (pun intended). Instead, I'll use a `Counter` of fish, and treat all the fish of each age group together. I have a hunch that part 2 will involve a ton-o'-fish." ] }, @@ -683,6 +761,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "- **Part 2**: How many lanternfish would there be after 256 days?\n", + "\n", "My hunch was right, so part 2 is simple:" ] }, @@ -703,7 +783,7 @@ } ], "source": [ - "answer(6.2, total(simulate(Fish(in6), 256)), 1592918715629)" + "answer(6.2, total(simulate(Fish(in6), 256)), 1_592_918_715_629)" ] }, { @@ -719,10 +799,7 @@ "source": [ "# [Day 7](https://adventofcode.com/2021/day/7): The Treachery of Whales\n", "\n", - "The input is a single line of comma-separated integers, each one the horizontal position of a crab (in its own submarine).\n", - "\n", - "1. Determine the horizontal position that the crabs can align to using the least fuel possible. How much fuel must they spend to align to that position? (Each unit of travel costs one unit of fuel.)\n", - "2. Determine the horizontal position that the crabs can align to using the least fuel possible so they can make you an escape route! How much fuel must they spend to align to that position? (Now for each crab the first unit of travel costs 1, the second 2, the third 3, and so on.) " + "- **Input**: The input is a single line of comma-separated integers, each one the horizontal position of a crab (in its own submarine)." ] }, { @@ -734,6 +811,13 @@ "in7 = parse(7, int, sep=',')" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 1**: Determine the horizontal position that the crabs can align to using the least fuel possible. How much fuel must they spend to align to that position? (Each unit of travel costs one unit of fuel.)" + ] + }, { "cell_type": "code", "execution_count": 23, @@ -760,6 +844,13 @@ "answer(7.1, fuel_cost(in7), 352707)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 2**: Determine the horizontal position that the crabs can align to using the least fuel possible so they can make you an escape route! How much fuel must they spend to align to that position? (Now for each crab the first unit of travel costs 1, the second 2, the third 3, and so on.) " + ] + }, { "cell_type": "code", "execution_count": 24, @@ -861,21 +952,11 @@ "source": [ "# [Day 8](https://adventofcode.com/2021/day/8): Seven Segment Search\n", "\n", - "Each entry in the input is 10 patterns followed by 4 output values, in the form:\n", + "- **Input**: Each entry in the input is 10 patterns followed by 4 output values, in the form:\n", "\n", " be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | edb cefdb eb gcbe\n", " \n", - "Each pattern and output value represents a digit, with each letter representing one of the segments in a [7-segment display](https://en.wikipedia.org/wiki/Seven-segment_display). The mapping of letters to segments is unknown, but is consistent within each entry.\n", - "\n", - "1. In the output values, how many times do digits 1, 4, 7, or 8 appear?\n", - "2. For each entry, determine all of the wire/segment connections and decode the four-digit output values. What do you get if you add up all of the output values?\n", - "\n", - "The entry above would be parsed as:\n", - "\n", - " (('be', 'cfbegad', 'cbdgef', 'fgaecd', 'cgeb', 'fdcge', 'agebfd', 'fecdb', 'fabcd', 'edb'), \n", - " ('edb', 'cefdb', 'eb', 'gcbe'))\n", - " \n", - "I refer to the left- and right-hand sides of the line with \"lhs\" and \"rhs\". " + "Each pattern and output value represents a digit, with each letter representing one of the segments in a [7-segment display](https://en.wikipedia.org/wiki/Seven-segment_display). The mapping of letters to segments is unknown, but is consistent within each entry." ] }, { @@ -884,43 +965,31 @@ "metadata": {}, "outputs": [], "source": [ - "in8 = parse(8, lambda line: mapt(atoms, line.split('|')))" + "def segment_parser(line) -> tuple: return mapt(atoms, line.split('|'))\n", + "\n", + "in8 = parse(8, segment_parser)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "def count1478(entries) -> int:\n", - " \"\"\"How many of the rhs digits in all the entries are a 1, 4, 7, or 8?\"\"\"\n", - " # (1, 4, 7, 8) have (2, 4, 3, 7) segments, respectively.\n", - " return sum(len(digit) in (2, 4, 3, 7) for (lhs, rhs) in entries for digit in rhs)\n", - "\n", - "answer(8.1, count1478(in8), 493)" + "assert (segment_parser('be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | edb cefdb eb gcbe')\n", + " == (('be', 'cfbegad', 'cbdgef', 'fgaecd', 'cgeb', 'fdcge', 'agebfd', 'fecdb', 'fabcd', 'edb'), \n", + " ('edb', 'cefdb', 'eb', 'gcbe')))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Part 2 is **tricky**. The digits other than 1, 4, 7, 8 are ambiguous. To figure out which is which in each entry I could do some fancy constraint satisfaction, but that sounds hard. Or I could exhaustively try all permutations of the segments. That sounds easy! Here's my plan:\n", - "- Make a list of the 7! = 5,040 possible string translators that permute `'abcdefg'`.\n", - "- Decode an entry by trying all translators and keeping the one that maps all of the ten lhs patterns to a valid digit. `decode` then applies the translator to the rhs and forms it into an `int`.\n", - " - Note that `get_digit` applies the translator, but then has to sort the letters to get a key that can be looked up in the `segment_map` dict.\n", - "- Finally, sum up the decoding of each entry." + "\n", + "\n", + "- **Part 1**: In the output values, how many times do digits 1, 4, 7, or 8 appear?\n", + "\n", + "That's the same as asking *how many values have 2, 4, 3, or 7 segments?*" ] }, { @@ -939,6 +1008,48 @@ "output_type": "execute_result" } ], + "source": [ + "def count1478(entries) -> int:\n", + " \"\"\"How many of the rhs digits in all the entries are a 1, 4, 7, or 8?\"\"\"\n", + " return sum(len(value) in (2, 4, 3, 7) for (lhs, rhs) in entries for value in rhs)\n", + "\n", + "answer(8.1, count1478(in8), 493)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 2**: For each entry, determine all of the wire/segment connections and decode the four-digit output values. What do you get if you add up all of the output values?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Part 2 is *tricky*. The value `'cefdb'` could be either a 2, 3, or 5. To figure out which one it is I could do some fancy constraint satisfaction, but that sounds hard. Or I could exhaustively try all permutations of the seven segments. That sounds easy! Here's my plan:\n", + "- Make a list of the 7! = 5,040 possible string translators that permute `'abcdefg'`.\n", + "- Decode an entry by trying all translators and keeping the one that maps all of the ten lhs patterns to a valid digit. `decode` then applies the translator to the rhs and forms it into an `int`.\n", + " - Note that `get_digit` must sort the translated letters to get a key that can be looked up in `segment_map`.\n", + "- Finally, sum up the decoding of each entry." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "segments7 = 'abcdefg'\n", "segment_map = {'abcefg': '0', 'cf': '1', 'acdeg': '2', 'acdfg': '3', 'bcdf': '4',\n", @@ -951,7 +1062,7 @@ " return segment_map.get(cat(sorted(pattern.translate(translator))))\n", "\n", "def decode(entry) -> int:\n", - " \"\"\"Decode an entry into an integer representing the 4 output digits\"\"\"\n", + " \"\"\"Decode an entry's rhs into a 4-digit integer.\"\"\"\n", " lhs, rhs = entry\n", " for t in translators:\n", " if all(get_digit(pattern, t) for pattern in lhs):\n", @@ -964,7 +1075,328 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# [Day 9](https://adventofcode.com/2021/day/9): ???" + "# [Day 9](https://adventofcode.com/2021/day/9): Smoke Basin\n", + "\n", + "- **Input:** The input is a *heightmap*: a 2D array of characters '0'–'9' representing the heights on the ocean floor. \n", + "\n", + "I'll use `parse` to get a tuple of strings such as `\"2199943210\"`, and then use `Grid` to define `g9` as a grid of `{location: height}` where `location` is an `(x, y)` Point and `height` is an int from 0 to 9." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [], + "source": [ + "in9 = parse(9)\n", + "\n", + "g9 = Grid({(x, y): int(height) \n", + " for x, row in enumerate(in9) \n", + " for y, height in enumerate(row)})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 1**: Find all of the low points on your heightmap. What is the sum of the risk levels of all low points on your heightmap?\n", + "\n", + "A low point is a point where all the neighbors are higher. The risk level is 1 more than the height of the low point." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def low_points(grid) -> List[Point]:\n", + " \"\"\"All low points on map M.\"\"\"\n", + " return [p for p in grid \n", + " if all(grid[p] < grid[nbr] for nbr in grid.neighbors(p))]\n", + "\n", + "def total_risk(grid) -> int:\n", + " \"\"\"Sum of height + 1 for all low points on map M.\"\"\"\n", + " return sum(grid[p] + 1 for p in low_points(grid))\n", + "\n", + "answer(9.1, total_risk(g9), 607)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 2**: What do you get if you multiply together the sizes of the three largest basins?\n", + " \n", + "I thought there was an ambiguity in the definition of *basin*: what happens if there is a high point that is not of height 9? The puzzle description says *Locations of height 9 do not count as being in any basin, and all other locations will always be part of exactly one basin.* I decided this must mean that basins are surrounded by either edges or height 9 locations.\n", + "\n", + "Given that definition of *basin,* I can associate each location with its low point using a [flood fill](https://en.wikipedia.org/wiki/Flood_fill) from each low point, and then get the sizes of the three largest (most common) basins." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def flood_fill(grid) -> Dict[Point, Point]:\n", + " \"\"\"Compute `basins` as a map of {point: low_point} for each point in grid.\"\"\"\n", + " basins = {} # A dict mapping each non-9 location to its low point.\n", + " def flood(p, low):\n", + " \"\"\"Spread from p in all directions until hitting a 9 or an edge;\n", + " mark each point p as being part of the basin starting at `low` point.\"\"\"\n", + " if grid[p] < 9 and p not in basins:\n", + " basins[p] = low\n", + " for p2 in grid.neighbors(p):\n", + " flood(p2, low)\n", + " for p in low_points(grid):\n", + " flood(p, p)\n", + " return basins\n", + "\n", + "def flood_size_products(grid, k=3) -> int:\n", + " \"\"\"The product of the sizes of the `k` largest basins.\"\"\"\n", + " basins = flood_fill(grid)\n", + " return prod(c for _, c in Counter(basins.values()).most_common(k))\n", + "\n", + "answer(9.2, flood_size_products(g9), 900864)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That's the correct answer; I'm done. But I do want to check that the set of low points is the same as the set of basins I identified:" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "249" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert set(low_points(g9)) == set(flood_fill(g9).values())\n", + "\n", + "len(low_points(g9))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "I would like to visualize the basins. I'll use a scatter plot with a color map that displays the 9 heights in yellow and the 0 heights in deep purple, with a gradient in between:" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "def show(g9, low=None):\n", + " plt.figure(figsize=(10, 10))\n", + " C = [g9[p] for p in g9]\n", + " plt.scatter(*transpose(g9), marker='s', s=10, c=C, cmap=plt.get_cmap('plasma'))\n", + " if low: plt.plot(*transpose(low_points(g9)), low, markersize=4)\n", + " plt.axis('square'); plt.axis('off')\n", + " \n", + "show(g9)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can optionally display the low points. Here I'll display them as black diamonds:" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "show(g9, 'kD')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Apropos to \"smoke in the water,\" and to the color scheme of my plot, Gary Grady's drawing for the day references Deep Purple.\n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 10](https://adventofcode.com/2021/day/10): Syntax Scoring\n", + "\n", + "- **Input**: Each entry in the input is a supposedly balanced string, but it may be:\n", + " - Corrupted: \"`{{([]<>)]}`\" should end in `}}`, not `]}`\n", + " - Incomplete: \"`{{([]<>)`\" needs to have `}}` added to be complete.\n", + "\n", + "I'll parse the entries as strs:" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [], + "source": [ + "in10 = parse(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 1**: Find the first illegal character in each corrupted line of the navigation subsystem. What is the total syntax error score for those errors?\n", + "\n", + "When the instructions for Part 1 say \"you can ignore these [incomplete] lines for now,\" I'm pretty sure we will need to deal with incomplete lines on Part 2. So I'll define `analyze_syntax` to return a tuple of two values: an error score and the expected characters for an incomplete line." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "error_scores = {')': 3, ']': 57, '}': 1197, '>': 25137}\n", + "open_close = {'(': ')', '[': ']', '{': '}', '<': '>'}\n", + "\n", + "def analyze_syntax(line) -> Tuple[int, str]:\n", + " \"\"\"A tuple of (error_score, expected_chars) for this line.\"\"\"\n", + " stack = [''] # A stack of closing characters we are looking for.\n", + " for c in line:\n", + " if c == stack[-1]:\n", + " stack.pop()\n", + " elif c in open_close:\n", + " stack.append(open_close[c])\n", + " else: # erroneous character\n", + " return error_scores[c], cat(reversed(stack))\n", + " return 0, cat(reversed(stack))\n", + " \n", + "answer(10.1, sum(analyze_syntax(line)[0] for line in in10), 367059)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 2**: Find the completion string for each incomplete line, score the completion strings, and sort the scores. What is the middle score?\n", + "\n", + "To score the completion string, we essentially treat it as a base-5 number, as shown in `completion_score`." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def completion_score(completion:str) -> int:\n", + " \"\"\"The completion score for the completion string.\"\"\"\n", + " score = completion.translate(str.maketrans(')]}>', '1234'))\n", + " return int(score, base=5)\n", + "\n", + "def median_completion_score(lines) -> int:\n", + " \"\"\"The median completion score out of all the uncorrupted lines.\"\"\"\n", + " return median(completion_score(completion) \n", + " for e, completion in map(analyze_syntax, lines) \n", + " if e == 0)\n", + "\n", + "answer(10.2, median_completion_score(in10), 1952146692)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 11](https://adventofcode.com/2021/day/11): ???" ] } ],