From a31e9a3f237f5e4d8b991bfea87681f38b5f33bc Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Thu, 16 Dec 2021 00:01:46 -0800 Subject: [PATCH] Add files via upload --- ipynb/Advent-2021.ipynb | 463 +++++++++++++++++++++++++++++++++++----- 1 file changed, 413 insertions(+), 50 deletions(-) diff --git a/ipynb/Advent-2021.ipynb b/ipynb/Advent-2021.ipynb index 2c515bc..57f17d8 100644 --- a/ipynb/Advent-2021.ipynb +++ b/ipynb/Advent-2021.ipynb @@ -30,7 +30,7 @@ "from itertools import permutations, combinations, chain, count as count_from, product as cross_product\n", "from typing import *\n", "from statistics import mean, median\n", - "from math import ceil\n", + "from math import ceil, inf\n", "from functools import lru_cache\n", "import matplotlib.pyplot as plt\n", "import re" @@ -195,7 +195,13 @@ " def neighbors(self, point) -> List[Point]:\n", " \"\"\"Points on the grid that neighbor `point`.\"\"\"\n", " x, y = point\n", - " return [(x+dx, y+dy) for (dx, dy) in self.deltas if (x+dx, y+dy) in self]" + " return [(x+dx, y+dy) for (dx, dy) in self.deltas \n", + " if (x+dx, y+dy) in self]\n", + " \n", + " def to_rows(self) -> List[List[object]]:\n", + " \"\"\"The contents of the grid in a rectangular list of lists.\"\"\"\n", + " return [[self[x, y] for x in range(self.width)]\n", + " for y in range(self.height)]" ] }, { @@ -204,7 +210,7 @@ "source": [ "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", "\n", "# [Day 1](https://adventofcode.com/2021/day/1): Sonar Sweep\n", "\n", @@ -720,7 +726,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "" + "" ] }, { @@ -969,7 +975,7 @@ "source": [ "That's over a trillion lanternfish. Latest [estimates](https://www.google.com/search?q=how+many+fish+are+in+the+sea) say that there are in fact trillions of fish in the sea. But not trillions of lanternfish, and not increasing from 300 to over a trillion in just 256 days.\n", "\n", - "" + "" ] }, { @@ -1179,7 +1185,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "" + "" ] }, { @@ -1232,7 +1238,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "- **Part 1**: In the output values, how many times do digits 1, 4, 7, or 8 appear?\n", "\n", @@ -1543,7 +1549,7 @@ "source": [ "Apropos to *Smoke* and *Water,* and to the color scheme of my plot, Gary Grady's drawing for the day references [Deep Purple](https://www.youtube.com/watch?v=_zO6lWfvM0g):\n", "\n", - "" + "" ] }, { @@ -1801,7 +1807,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "" + "" ] }, { @@ -1933,7 +1939,9 @@ "\n", "- **Input**: The input is in two sections: (1) a set of dots, e.g. \"`6,10`\". (2) a list of fold instructions, e.g. \"`fold along y=7`\".\n", "\n", - "My `parse` command is not set up to parse different sections differently, so I'll only ask `parse` to do a minimal amount of work, just parsing the input file into two sections, and splitting each section into a list of lines. " + "My `parse` command is not set up to parse different sections differently, so I'll only ask `parse` to do a minimal amount of work, just parsing the input file into two sections, each a list of lines. Then I'll further process the two sections to get two variables:\n", + "- `dots`: a set of `(x, y)` points, such as `(103, 224)`. \n", + "- `folds`: a list of fold instructions, each implemented as a tuple, such as `('y', 7)` or `('x', 15)`." ] }, { @@ -1958,24 +1966,7 @@ } ], "source": [ - "in13 = parse(13, str.splitlines, sep='\\n\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then I'll further process the two sections to get two variables:\n", - "- `dots`: a set of `(x, y)` points, such as `(103, 224)`. \n", - "- `folds`: a list of fold instructions, each implemented as a tuple, such as `('y', 7)` or `('x', 15)`." - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "metadata": {}, - "outputs": [], - "source": [ + "in13 = parse(13, str.splitlines, sep='\\n\\n')\n", "dots = {ints(line) for line in in13[0]}\n", "folds = [(words(line)[2], ints(line)[0]) for line in in13[1]]" ] @@ -1991,7 +1982,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 50, "metadata": {}, "outputs": [], "source": [ @@ -2006,7 +1997,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 51, "metadata": {}, "outputs": [ { @@ -2015,7 +2006,7 @@ "True" ] }, - "execution_count": 52, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -2033,7 +2024,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 52, "metadata": {}, "outputs": [], "source": [ @@ -2047,7 +2038,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 53, "metadata": {}, "outputs": [ { @@ -2056,7 +2047,7 @@ "True" ] }, - "execution_count": 54, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" }, @@ -2081,7 +2072,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "I kind of cheated here. I didn't want to write an OCR program, so I relied on my own eyes to look at the dots and see the code." + "I kind of cheated here. I didn't want to write an OCR program, so I relied on my own eyes to look at the dots and see the code.\n", + "\n", + "**Note**: My transparent paper was folded 12 times. Is that physically feasible? [Britney Gallivan](https://www.youtube.com/watch?v=AfPDvhKvaa0&) says yes (barely)." ] }, { @@ -2104,7 +2097,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 54, "metadata": {}, "outputs": [ { @@ -2140,7 +2133,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 55, "metadata": {}, "outputs": [ { @@ -2149,7 +2142,7 @@ "True" ] }, - "execution_count": 56, + "execution_count": 55, "metadata": {}, "output_type": "execute_result" } @@ -2166,8 +2159,8 @@ "\n", "def quantity_diff(polymer) -> int:\n", " \"\"\"The count of most common element minus the count of least common element.\"\"\"\n", - " [(_, most), *_, (_, least)] = Counter(polymer).most_common()\n", - " return most - least\n", + " counts = list(Counter(polymer).values())\n", + " return max(counts) - min(counts)\n", "\n", "assert pairs('NNCB') == ['NN', 'NC', 'CB']\n", "assert pair_insertion('NNCB', rules={'NN': 'C', 'NC': 'B', 'CB': 'H'}, steps=1) == 'NCNBCHB'\n", @@ -2188,7 +2181,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 56, "metadata": {}, "outputs": [ { @@ -2197,7 +2190,7 @@ "Counter({'NN': 1, 'NC': 1, 'CB': 1})" ] }, - "execution_count": 57, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -2217,7 +2210,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 57, "metadata": {}, "outputs": [], "source": [ @@ -2235,7 +2228,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 58, "metadata": {}, "outputs": [], "source": [ @@ -2250,16 +2243,16 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 59, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Counter({'C': 1, 'N': 2, 'B': 1})" + "Counter({'B': 1, 'N': 2, 'C': 1})" ] }, - "execution_count": 60, + "execution_count": 59, "metadata": {}, "output_type": "execute_result" } @@ -2277,7 +2270,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 60, "metadata": {}, "outputs": [], "source": [ @@ -2293,7 +2286,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 61, "metadata": {}, "outputs": [ { @@ -2302,7 +2295,7 @@ "True" ] }, - "execution_count": 62, + "execution_count": 61, "metadata": {}, "output_type": "execute_result" } @@ -2334,6 +2327,376 @@ "answer(14.1, pair_insertion_diff(polymer, rules, 10), 3_259)\n", "answer(14.2, pair_insertion_diff(polymer, rules, 40), 3_459_174_981_021)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 15](https://adventofcode.com/2021/day/15): Chiton\n", + "\n", + "- **Input**: The input is a square grid of *risk levels*, each one digit, 1–9." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "First 7 lines of Day 15 input (which is parsed into 100 entries):\n", + "----------------------------------------------------------------\n", + "4249856395422795894919869133487611581179923326874763428673979547991221931142777981153991369468629849\n", + "5812974178739823463799939791688998895568796557798392761499941349143539572865883254186633218867928826\n", + "3699989976298596286299499129934993241824395574879938998946914116375199242199151918863674914554714898\n", + "5682435936794718871685718386458294198391116125679589438794914499278679393779734596558953699438589518\n", + "7681197997388219696918569664119968498599547892968929425479817979816979144947916716989874825679487436\n", + "9981166198272997899142698141878643123757515999788822988261499197559193945291512682763935126815448215\n", + "8849481991861599951293183728419792414164347979985169641698899853377259811688489269959429131918919179\n" + ] + } + ], + "source": [ + "in15 = Grid(rows=parse(15, digits))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 1**: You start in the top left position, your destination is the bottom right position, and you cannot move diagonally. What is the lowest total risk of any path from the top left to the bottom right? (Don't count the risk level of your starting position.)\n", + "\n", + "I'll use a search that updates a grid of the `cost` of the best known path from start to each point. The cost for each point is initially infinite (because we don't know any paths), and is updated each time we find a better path to the point." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def search_grid(grid, start=(0, 0), goal=None) -> int:\n", + " \"\"\"The total cost of the best path from start to goal (which defaults to bottom right).\"\"\"\n", + " goal = goal or max(grid)\n", + " cost = Grid({p: inf for p in grid}) # cost[p] is cost of best known path from start to p\n", + " frontier = {start}\n", + " while frontier:\n", + " p = frontier.pop()\n", + " new_cost = 0 if p is start else (grid[p] + min(cost[b] for b in grid.neighbors(p)))\n", + " if new_cost < cost[p]:\n", + " cost[p] = new_cost\n", + " frontier.update(grid.neighbors(p))\n", + " return cost[goal]\n", + "\n", + "answer(15.1, search_grid(in15), 687)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 2**: The entire cave is actually five times larger in both dimensions. Your original map tile repeats to the right and downward; each time the tile repeats, all of its risk levels are 1 higher than the tile immediately up or left of it. However, risk levels above 9 wrap back around to 1. Using the full map, what is the lowest total risk of any path from the top left to the bottom right?\n", + "\n", + "Here's how to define the full map of the cave:" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": {}, + "outputs": [], + "source": [ + "def repeat_grid(grid, repeat=5):\n", + " \"\"\"Extend the grid to be `repeat` times larger in both directions.\n", + " Values within each repeated block are increased by 1 for each repetition to the right or down,\n", + " but values over 9 wrap around to 1.\"\"\"\n", + " w, h = grid.width, grid.height \n", + " return Grid([(x + xr * w, y + yr * h), clock_mod(grid[x, y] + xr + yr, 9)]\n", + " for xr in range(repeat) \n", + " for yr in range(repeat)\n", + " for x, y in grid)\n", + "\n", + "def clock_mod(i, m) -> int:\n", + " \"\"\"i % m, but replace a result of 0 with m\"\"\"\n", + " # This is like a clock, where 24 mod 12 is 12, not 0.\n", + " return (i % m) or m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With 250,000 points in the full map, I think it will take several minutes (not hours, not seconds), to find the optimal path with the `search_grid` algorithm. I really wish I had used A* search; I think it would run in just a few seconds." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 4min 21s, sys: 271 ms, total: 4min 22s\n", + "Wall time: 4min 22s\n" + ] + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%time answer(15.2, search_grid(repeat_grid(in15, 5)), 2957)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "OK, 5 minutes wasn't so bad. I may come back and improve on this later (or maybe not).\n", + "\n", + "Gary Grady's drawing represents the risk involved in finding a path that avoids bumping into the ceiling above or the chitons below.\n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 16](https://adventofcode.com/2021/day/16): Packet Decoder\n", + "\n", + "- **Input**: The input is a single line containing a sequence of hexadecimal digits, a message using the Buoyancy Interchange Transmission System (BITS). For now I will leave the input as a string of hex digits." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "First 1 lines of Day 16 input (which is parsed into 1 entries):\n", + "--------------------------------------------------------------\n", + "220D790065B2745FF004672D99A34E5B33439D96CEC80373C0068663101A98C406A5E7395DC1804678BF25A4093BFBDB886C ...\n" + ] + } + ], + "source": [ + "in16 = parse(16)[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 1:** The puzzle is to parse this hexadecimal transmission into data packets, according to the rules contained in [the instructions](https://adventofcode.com/2021/day/16), and add up all of the version numbers.\n", + "\n", + "The gist of [the instructions](https://adventofcode.com/2021/day/16) is to consider the hexadecimal sequence as a bit string, parse the bit string into bit fields, and construct nested packets based on the values of the fields. Here are basic types for `Bits` (bit string) and `Packet`, along with functions to convert from a hexadecimal string to a bit string, and from there to an int: " + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": {}, + "outputs": [], + "source": [ + "Bits = str # a string of '0's and '1's\n", + "Packet = namedtuple('Packet', 'V, T, contents') # V is version; T is type ID\n", + "\n", + "def bits_from_hex(hex) -> Bits: \n", + " \"\"\"Convert a hexadecimal string into a bit string, making sure each hex digit is 4 bits.\"\"\"\n", + " return cat(f'{int(x, 16):04b}' for x in hex)\n", + "\n", + "def int2(bits: Bits) -> int: \n", + " \"\"\"Convert a bit string into an int.\"\"\"\n", + " return int(bits, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To parse the bit string into packets, I will have various functions that start with the word `parse_` and return a tuple of two values: the object parsed (either an int or a packet) and the remaining bits that were not parsed." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": {}, + "outputs": [], + "source": [ + "def parse_int(L, bits) -> Tuple[int, Bits]:\n", + " \"\"\"Parse an integer from the first L bits; return the int and the remaining bits.\"\"\"\n", + " return int2(bits[:L]), bits[L:]\n", + "\n", + "def parse_packet(bits) -> Tuple[Packet, Bits]:\n", + " \"\"\"Parse a packet; return it and the remaining bits.\"\"\"\n", + " V, T, bits = int2(bits[0:3]), int2(bits[3:6]), bits[6:]\n", + " parser = parse_literal_packet if T == 4 else parse_operator_packet\n", + " return parser(V, T, bits)\n", + " \n", + "def parse_literal_packet(V, T, bits) -> Tuple[Packet, Bits]:\n", + " \"\"\"Build a packet with a literal value; return it and the remaining bits.\"\"\"\n", + " literal = ''\n", + " while True:\n", + " bit, nibble, bits = bits[0], bits[1:5], bits[5:]\n", + " literal += nibble\n", + " if bit == '0':\n", + " return Packet(V, T, int2(literal)), bits\n", + " \n", + "def parse_operator_packet(V, T, bits) -> Tuple[Packet, Bits]:\n", + " \"\"\"Build a packet with subpackets; return it and the remaining bits.\"\"\"\n", + " I, bits = parse_int(1, bits)\n", + " subpackets = [] \n", + " if I == 0:\n", + " L, bits = parse_int(15, bits)\n", + " target = len(bits) - L\n", + " while len(bits) > target:\n", + " packet, bits = parse_packet(bits)\n", + " subpackets.append(packet)\n", + " else:\n", + " L, bits = parse_int(11, bits)\n", + " for _ in range(L):\n", + " packet, bits = parse_packet(bits)\n", + " subpackets.append(packet) \n", + " return Packet(V, T, subpackets), bits" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we're ready to solve the puzzle by summing up the version numbers of the parsed packet and its subpackets:" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def subpackets(packet) -> List[Packet]: \n", + " \"\"\"The subpackets of a packet.\"\"\"\n", + " return packet.contents if isinstance(packet.contents, list) else []\n", + "\n", + "def sum_versions(packet) -> int:\n", + " \"\"\"The sum of the version numbers of this packet and all its subpackets (recursively).\"\"\"\n", + " return packet.V + sum(sum_versions(p) for p in subpackets(packet))\n", + " \n", + "packet16, _ = parse_packet(bits_from_hex(in16))\n", + "answer(16.1, sum_versions(packet16), 989)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This was way more code than previous days! Here are some assertions I used to make sure I was on the right track:" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": {}, + "outputs": [], + "source": [ + "assert (bits_from_hex('D2FE28') \n", + " == '110100101111111000101000')\n", + "\n", + "assert (bits_from_hex('38006F45291200') \n", + " == '00111000000000000110111101000101001010010001001000000000')\n", + "\n", + "assert (parse_packet('110100101111111000101000') \n", + " == (Packet(V=6, T=4, contents=2021), '000'))\n", + "\n", + "assert (parse_packet('00111000000000000110111101000101001010010001001000000000')\n", + " == (Packet(V=1, T=6, contents=[Packet(V=6, T=4, contents=10), \n", + " Packet(V=2, T=4, contents=20)]),\n", + " '0000000'))\n", + "\n", + "assert (parse_packet('11101110000000001101010000001100100000100011000001100000')\n", + " == (Packet(V=7, T=3, contents=[Packet(V=2, T=4, contents=1), \n", + " Packet(V=4, T=4, contents=2), \n", + " Packet(V=1, T=4, contents=3)]),\n", + " '00000'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **Part 2**: What do you get if you evaluate the expression represented by your hexadecimal-encoded BITS transmission?\n", + "\n", + "The evaluation rules are that a literal packet evaluates to the number that is its contents, and an operator packet applies an operator determined by the type id (in the `packet.T` field) to the list of values formed by evaluating the subpackets. I put the operators into the `packet_ops` dict." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def eval_packet(packet) -> int:\n", + " \"\"\"Evaluate a packet according to the operator rules.\"\"\"\n", + " if packet.T == 4:\n", + " return packet.contents\n", + " else:\n", + " vals = [eval_packet(p) for p in subpackets(packet)]\n", + " return packet_ops[packet.T](vals)\n", + " \n", + "packet_ops = {0: sum, 1: prod, 2: min, 3: max, \n", + " 5: lambda v: int(v[0] > v[1]), \n", + " 6: lambda v: int(v[0] < v[1]), \n", + " 7: lambda v: int(v[0] == v[1])}\n", + "\n", + "answer(16.2, eval_packet(packet16), 7936430475134)" + ] } ], "metadata": {