{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
Peter Norvig
December 1–25, 2021
\n", "\n", "# Advent of Code 2021\n", "\n", "I'm doing [Advent of Code](https://adventofcode.com/) (AoC) this year. I'm not competing for points\\, just for fun.\n", "\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", "# Day 0: Preparations\n", "\n", "First, imports that I have used in past AoC years:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from __future__ import annotations\n", "from collections import Counter, defaultdict, namedtuple, deque\n", "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", "\n", "import matplotlib.pyplot as plt\n", "import functools\n", "import math\n", "import re" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "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", " 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", " assert got == expected, f'For {puzzle_number}, expected {expected} but got {got}.'\n", " return True\n", "\n", "def ints(text: str) -> Tuple[int]:\n", " \"\"\"A tuple of all the integers in text, ignoring non-number characters.\"\"\"\n", " return mapt(int, re.findall('-?[0-9]+', text))\n", "\n", "Atom = Union[float, int, str]\n", "\n", "def atoms(text: str, sep=None) -> Tuple[Atom]:\n", " \"\"\"A tuple of all the atoms (numbers or strs) in text.\n", " By default, atoms are space-separated but you can change that with `sep`.\"\"\"\n", " return tuple(map(atom, text.split(sep)))\n", "\n", "def atom(text: str) -> Atom:\n", " \"\"\"Parse text into a single float or int or str.\"\"\"\n", " try:\n", " x = float(text)\n", " return round(x) if round(x) == x else x\n", " except ValueError:\n", " return text\n", " \n", "def mapt(fn, *args):\n", " \"\"\"map(fn, *args) and return the result as a tuple.\"\"\"\n", " return tuple(map(fn, *args))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A few additional utility functions that I have used in the past:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def quantify(iterable, pred=bool) -> int:\n", " \"Count the number of items in iterable for which pred is true.\"\n", " return sum(1 for item in iterable if pred(item))\n", "\n", "def multimap(items: Iterable[Tuple]) -> dict:\n", " \"Given (key, val) pairs, return {key: [val, ....], ...}.\"\n", " result = defaultdict(list)\n", " for (key, val) in items:\n", " result[key].append(val)\n", " return result\n", "\n", "def prod(numbers) -> float: # Will be math.prod in Python 3.8\n", " \"The product of an iterable of numbers.\" \n", " result = 1\n", " for n in numbers:\n", " result *= n\n", " return result\n", "\n", "def total(counts: Counter) -> int: \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))\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": [ "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", "# [Day 1](https://adventofcode.com/2021/day/1): Sonar Sweep\n", "\n", "\n", "- **Input**: Each entry in the input is an integer depth measurement, such as \"`148`\".\n" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "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, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def increases(nums: List[int]) -> int:\n", " \"\"\"How many numbers are bigger than the previous one?\"\"\"\n", " return quantify(nums[i] > nums[i - 1] \n", " for i in range(1, len(nums)))\n", "\n", "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, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def window_increases(nums: List[int], w=3) -> int:\n", " \"\"\"How many sliding windows of w numbers have a sum bigger than the previous window?\"\"\"\n", " return quantify(sum(nums[i:i+w]) > sum(nums[i-1:i-1+w])\n", " for i in range(1, len(nums) + 1 - w))\n", "\n", "answer(1.2, window_increases(in1), 1429)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# [Day 2](https://adventofcode.com/2021/day/2): Dive! \n", "\n", "- **Input**: Each entry in the input is a command, like \"`forward 1`\", \"`down 2`\", or \"`up 3`\".\n", "\n", "I'll parse a command into a tuple like `('forward', 1)`." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "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, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def drive(commands) -> int:\n", " \"\"\"What is the product of position and depth after following commands?\"\"\"\n", " pos = depth = 0\n", " for (op, n) in commands:\n", " if op == 'forward': pos += n\n", " if op == 'down': depth += n\n", " if op == 'up': depth -= n\n", " return pos * depth\n", "\n", "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, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def drive2(commands) -> int:\n", " \"\"\"What is the product of position and depth after following commands?\n", " This time we have to keep track of `aim` as well.\"\"\"\n", " pos = depth = aim = 0\n", " for (op, n) in commands:\n", " if op == 'forward': pos += n; depth += aim * n\n", " if op == 'down': aim += n\n", " if op == 'up': aim -= n\n", " return pos * depth\n", "\n", "answer(2.2, drive2(in2), 1954293920)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# [Day 3](https://adventofcode.com/2021/day/3): Binary Diagnostic\n", "\n", "- **Input**: Each entry in the input is a bit string, such as \"`101000111100`\".\n", "\n", "I'll parse them as strings; I won't convert them into ints." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "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)?" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def common(strs, i) -> str: \n", " \"\"\"The bit that is most common in position i.\"\"\"\n", " bits = [s[i] for s in strs]\n", " return '1' if bits.count('1') >= bits.count('0') else '0'\n", "\n", "def uncommon(strs, i) -> str: \n", " \"\"\"The bit that is least common in position i.\"\"\"\n", " return '1' if common(strs, i) == '0' else '0'\n", "\n", "def epsilon(strs) -> str:\n", " \"\"\"The bit string formed from most common bit at each position.\"\"\"\n", " return cat(common(strs, i) for i in range(len(strs[0])))\n", "\n", "def gamma(strs) -> str:\n", " \"\"\"The bit string formed from most uncommon bit at each position.\"\"\"\n", " return cat(uncommon(strs, i) for i in range(len(strs[0])))\n", "\n", "def power(strs) -> int: \n", " \"\"\"Product of epsilon and gamma rates.\"\"\"\n", " return int(epsilon(strs), 2) * int(gamma(strs), 2)\n", " \n", "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, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def select_str(strs, common_fn, i=0) -> str:\n", " \"\"\"Select a str from strs according to common_fn:\n", " Going left-to-right, repeatedly select just the strs that have the right i-th bit.\"\"\"\n", " if len(strs) == 1:\n", " return strs[0]\n", " else:\n", " bit = common_fn(strs, i)\n", " selected = [s for s in strs if s[i] == bit]\n", " return select_str(selected, common_fn, i + 1)\n", "\n", "def life_support(strs) -> int: \n", " \"\"\"The product of oxygen (most common select) and CO2 (least common select) rates.\"\"\"\n", " return int(select_str(strs, common), 2) * int(select_str(strs, uncommon), 2)\n", " \n", "answer(3.2, life_support(in3), 6775520)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# [Day 4](https://adventofcode.com/2021/day/4): Giant Squid\n", "\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", "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`. " ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "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, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Board = Tuple[int]\n", "Line = List[int]\n", "B = 5\n", "def sq(x, y) -> int: \"The index number of the square at (x, y)\"; return x + B * y\n", "\n", "def lines(square) -> Tuple[Line, Line]:\n", " \"\"\"The two lines through square number `square`.\"\"\"\n", " return ([sq(x, square // B) for x in range(B)], \n", " [sq(square % B, y) for y in range(B)])\n", "\n", "def bingo_winners(boards, drawn, just_called) -> List[Board]:\n", " \"\"\"Boards that win due to the number just called.\"\"\"\n", " def filled(board, line) -> bool: return all(board[n] in drawn for n in line)\n", " return [board for board in boards\n", " if just_called in board\n", " and any(filled(board, line) for line in lines(board.index(just_called)))]\n", "\n", "def bingo_score(board, drawn, just_called) -> int:\n", " \"\"\"Sum of unmarked numbers multiplied by the number just called.\"\"\"\n", " unmarked = sum(n for n in board if n not in drawn)\n", " return unmarked * just_called\n", "\n", "def bingo(boards, order) -> int: \n", " \"\"\"What is the final score of the first winning board?\"\"\"\n", " drawn = set()\n", " for num in order:\n", " drawn.add(num)\n", " winners = bingo_winners(boards, drawn, num)\n", " if winners:\n", " return bingo_score(winners[0], drawn, num)\n", "\n", "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, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def bingo_last(boards, order) -> int: \n", " \"\"\"What is the final score of the last winning board?\"\"\"\n", " boards = set(boards)\n", " drawn = set()\n", " for num in order:\n", " drawn.add(num)\n", " winners = bingo_winners(boards, drawn, num)\n", " boards -= set(winners)\n", " if not boards:\n", " return bingo_score(winners[-1], drawn, num)\n", " \n", "answer(4.2, bingo_last(boards, order), 26936)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# [Day 5](https://adventofcode.com/2021/day/5): Hydrothermal Venture\n", "\n", "- **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)`." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "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, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def points(line) -> bool:\n", " \"\"\"All the (integer) points on a line.\"\"\"\n", " x1, y1, x2, y2 = line\n", " if x1 == x2:\n", " return [(x1, y) for y in cover(y1, y2)]\n", " elif y1 == y2:\n", " return [(x, y1) for x in cover(x1, x2)]\n", " else: # non-orthogonal lines not allowed\n", " return []\n", " \n", "def cover(x1, x2) -> range:\n", " \"\"\"All the ints from x1 to x2, inclusive, with x1, x2 in either order.\"\"\"\n", " return range(min(x1, x2), max(x1, x2) + 1)\n", "\n", "def overlaps(lines) -> int:\n", " \"\"\"How many points overlap 2 or more lines?\"\"\"\n", " counts = Counter(flatten(map(points, lines)))\n", " return quantify(counts[p] >= 2 for p in counts)\n", "\n", "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": {}, "source": [ "For part 2 I'll redefine `points` and `overlaps` in a way that doesn't break part 1:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def points(line, diagonal=False) -> bool:\n", " \"\"\"All the (integer) points on a line; optionally allow diagonal lines.\"\"\"\n", " x1, y1, x2, y2 = line\n", " if diagonal or x1 == x2 or y1 == y2:\n", " dx, dy = sign(x2 - x1), sign(y2 - y1)\n", " length = max(abs(x2 - x1), abs(y2 - y1))\n", " return [(x1 + k * dx, y1 + k * dy) for k in range(length + 1)]\n", " else: # non-orthogonal lines not allowed when diagonal is False\n", " return []\n", " \n", "def overlaps(lines, diagonal=False) -> int:\n", " \"\"\"How many points overlap 2 or more lines?\"\"\"\n", " counts = Counter(flatten(points(line, diagonal) for line in lines))\n", " return quantify(counts[p] >= 2 for p in counts)\n", "\n", "assert points((1, 1, 1, 3), False) == [(1, 1), (1, 2), (1, 3)]\n", "assert points((1, 1, 3, 3), False) == []\n", "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, False), 7436)\n", "answer(5.2, overlaps(in5, True), 21104)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# [Day 6](https://adventofcode.com/2021/day/6): Lanternfish\n", "\n", "- **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." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "in6 = parse(6, int, sep=',')" ] }, { "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." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Fish = Counter # Represent a school of fish as a Counter of their timer-ages\n", "\n", "def simulate(fish, days=1) -> Fish:\n", " \"\"\"Simulate the aging and birth of fish over `days`.\"\"\"\n", " for day in range(days):\n", " fish = Fish({t - 1: fish[t] for t in fish})\n", " if -1 in fish: # births\n", " fish[6] += fish[-1]\n", " fish[8] = fish[-1]\n", " del fish[-1]\n", " return fish\n", " \n", "assert simulate(Fish((3, 4, 3, 1, 2))) == Fish((2, 3, 2, 0, 1))\n", "assert simulate(Fish((2, 3, 2, 0, 1))) == Fish((1, 2, 1, 6, 0, 8))\n", "\n", "answer(6.1, total(simulate(Fish(in6), 80)), 350917)" ] }, { "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:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "answer(6.2, total(simulate(Fish(in6), 256)), 1_592_918_715_629)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# [Day 7](https://adventofcode.com/2021/day/7): The Treachery of Whales\n", "\n", "- **Input**: The input is a single line of comma-separated integers, each one the horizontal position of a crab (in its own submarine)." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "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, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def fuel_cost(positions) -> int:\n", " \"\"\"How much fuel does it cost to get everyone to the best alignment point?\"\"\"\n", " # I happen to know that the best alignment point is the median\n", " align = median(positions)\n", " return sum(abs(p - align) for p in positions)\n", "\n", "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, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def fuel_cost2(positions) -> int:\n", " \"\"\"How much fuel does it cost to get everyone to the best alignment point, \n", " with nonlinear fuel costs?\"\"\"\n", " # I don't know the best alignment point, so I'll try all of them\n", " return min(sum(burn_rate2(p, align) for p in positions)\n", " for align in range(min(positions), max(positions) + 1))\n", "\n", "def burn_rate2(p, align) -> int:\n", " \"\"\"The first step costs 1, the second 2, etc. (i.e. triangular numbers).\"\"\"\n", " steps = abs(p - align)\n", " return steps * (steps + 1) // 2\n", "\n", "answer(7.2, fuel_cost2(in7), 95519693)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Note**: Now that I got the right answer and have some time to think about it, if the travel cost were exactly quadratic, we would be minimizing the sum of square distances, and Legendre and Gauss knew that the **mean**, not the **median**, is the alignment point that does that. What's the mean of the positions?" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "490.543" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "positions = in7\n", "mean(positions)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's not an integer, but I'll try it, along with the integers above and below it:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{490: 95519693, 491: 95519725, 490.543: 95519083.0}" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{align: sum(burn_rate2(p, align) for p in positions)\n", " for align in [490, 491, mean(positions)]}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that rounding down gives the right answer, rounding up does a bit worse, and using the exact mean gives a total fuel cost that is *better* than the correct answer (but is apparently not a legal alignment point)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# [Day 8](https://adventofcode.com/2021/day/8): Seven Segment Search\n", "\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." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "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": [], "source": [ "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": [ "\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?*" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 29, "metadata": {}, "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", " 'abdfg': '5', 'abdefg': '6', 'acf': '7', 'abcdefg': '8', 'abcdfg': '9'}\n", "\n", "translators = [str.maketrans(segments7, cat(p)) for p in permutations(segments7)]\n", "\n", "def get_digit(pattern, translator) -> Optional[Char]:\n", " \"\"\"Translate the pattern, and return a digit '0' to '9' if valid.\"\"\"\n", " return segment_map.get(cat(sorted(pattern.translate(translator))))\n", "\n", "def decode(entry) -> int:\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", " return int(cat(get_digit(pattern, t) for pattern in rhs))\n", "\n", "answer(8.2, sum(map(decode, in8)), 1010460)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# [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": "iVBORw0KGgoAAAANSUhEUgAAAi0AAAIuCAYAAABzfTjcAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjMsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+AADFEAAAgAElEQVR4nO2dT8il53mf74yt4JGYUTGSKyEaZBVs8B9UR8jU1DHCm5Y4m4JrFy+CjOuSRXfdyIUYIVNLXZgu0kWwaetmIVxj8CYJziYEkxCTVm5FLYMNloWD0GCJ4szBntJxxl10dPSc53sm5zfzu9/7OU/mujbDe53zfef9/x2Y69znl37xi18EAAAAwKlzbvYKAAAAACjwpgUAAACWgDctAAAAsAS8aQEAAIAl4E0LAAAALAFvWgAAAGAJeNMCAAAAS8CbFgAAAFgC3rQAAADAEvCmBQAAAJaANy0AAACwBLxpAQAAgCXgTQsAAAAsAW9aAAAAYAl40wIAAABLwJsWAAAAWALetAAAAMAS8KYFAAAAloA3LQAAALAEvGkBAACAJeBNCwAAACwBb1oAAABgCXjTAgAAAEvAmxYAAABYAt60AAAAwBLwpgUAAACWgDctAAAAsAS8aQEAAIAl4E0LAAAALMGbZ73wH//Dpy9HxIVG7f7gLx6Kxu2+cO3jF//q/z7UPm939y+/ePHaf3vngTv36PeGz/vHb/7ygfujnz9+8V+f+6+X+9fo1mX34W99Rn7dn3/5Hx24Nz/+Zxf/97/56IF76+e/Nnyeuh3qa4y24z/e958P3KcufVLeB9/75//qwL3zK/8hfducfTBaP3V7R+eGeg5dPP/Mgbt85Qn5XFP3gbqf1X3lXDOj7VW3Y3SMnPN0tH4V56nqss/7inVR9/3oedn3TmfbKu6Tzv3glO6xt/p366efeCWa5f1rRCHT3rTE4Yb/TcsXcDgcDofDnYSLI25T+O8hAAAAWALetAAAAMASzHzTshss7waP43A4HA6Hm+/a5f7xEqY1LT986b4z7jvnrkg/e+2Few6Wzz0aceH5Ow6f9GjEV3/2VPeTj8dn/89nOvfxeOSbX+rcZ4a/b/S6l79//4F7a0S8+uJ9Z9zoebuHrx64u0Nn9BqjffrdH995xo328+hn733o0hl38R2vnHHn3v3aGTfatku/+euH7iv6sbwegB38vtH6ffRHn+vMJ+Mj73/xzPNG54Z6Dn3g6tl99Z5r58+40bmmnlfqOTTap6PnOdfMX/7VFzv3hLwdo2P0wIMvn3EffOy5zoyP22g//+mfPHKw/M7Q95+6X1RGv++vn/zVwyd9fvw8ldH63fXs4bbF4xE/+fp7D9RbHx2fL29/8OwxetfbfnbGja6t7HvnaDuyGZ1/6j4YnX/q/WB0j335pQcOlrc4d0f34lv9u3Xx2TObsMkx+psgxMXhcDgcDqe6OOI2haYFAAAAloCmBYfD4XA4nOLa5f7xEn7pF7/4RfVrAgAAANw0JzUR93rU9bq7qWmho+epzpk6qU78zJ5UmD1pdPQ8Zwqjui7ORMjsiaTZE1MrpnY6UzZnTWB1znF1H5zSVNHsc1xdP/XeOVq/7Km2o9dQnXruzjpGo/upen92Jqpn/40aXVv977se+h783T736PeYiNstn1KAhMPhcDjc7eziiNsUQlwAAABYAkJcHA6Hw+FwimuX+8dLIMQFAACAJZjWtHSBT0SciXxuGFKNwiInaMqO9tSwLTtwVINO1Tmh1yjqGn1duxoAZ8e0o32VfR5kH99R8KdG6dkRavb2Otelej+oiKrV6NGJXyvOtVkfEsgOy53tmBX7zorclX1/fcL1wd/tt37+a4S43fIpBUg4HA6Hw93OLo64TSHEBQAAgCUgxMXhcDgcDqe4drl/vARCXAAAAFiCaU1LFxZFROyuf5X6686OurKjODWQUuPI0e9TphJuEYRlx1+j11An8WZvrxP8OZFs9rZVBJ3ZU0+zA0f1epsVUDvBqbPvK1x2DFoxmVt1s6YlO9uh3l8y/y7c9ez90Szvf18UQoiLw+FwOBxOdXHEbQohLgAAACwBIS4Oh8PhcDjFtcv94yUQ4gIAAMASTGtaujgoImJ3+fsHkc8NJ7qOAqRR1DWawDp6XsUkStWp25EdUaoBlxMjq/FXdgDsTNmcNVnVCaizXXbc7JxXznmaHaGqx0gNTk99SvOsibhODO/cO534NTv2He0X52/ZrV6Du4evRrO8f14UQoiLw+FwOBxOdXHEbQohLgAAACwBIS4Oh8PhcDjFtcv94yUQ4gIAAMASnNRE3Gsv3BONu6nQaxRIZU8kzZ6Q6MRfozCrIrJzgj/1NbIDYDUGHf0+55g7xyN70rL6GmpEqcalFROoK56nTgAeba/6gQAnGM/ef9nTZbOnk6u/z9kH6hTfi+efOXCXrzyRvu+zP2hyq38Xrv978Hebibhnl08pQMLhcDgc7nZ2ccRtCiEuAAAALAEhLg6Hw+FwOMW1y/3jJRDiAgAAwBJMa1q6ECjijXdsr7sbxlDZEZYaiKohpBOcqrGWEzM6ky2zA9YVvxJ+1pRSJ1BW94s6uVSNALNjeDXynHU+q9O6R8+ruG/MmqbrTHg+pQnUzrVQEUs7fweVfXDvQ5eiWd4/LwohxMXhcDgcDqe6OOI2hRAXAAAAloAQF4fD4XA4nOLa5f7xEghxAQAAYAmmNS1dvBQRsfvqz56Kxm0SjaphpfP16qOfzZ5smT2JV40y1TDQiUazn5c9ediJoNVJqBVBono+q1NAR0FiRRypHiP1fM6OX9XzqiLWz34NNTjNjshnxbnqPnDuJdnHSN0O5TW6qfX750UhhLg4HA6Hw+FUF0fcphDiAgAAwBIQ4uJwOBwOh1Ncu9w/XgIhLgAAACzBtKalC5UiInYf/dHnonE3FVZmT1Z1IsXsCKtiEqW6vc52OFM71ThN3VdOqOmca2qsWhFkO+eBcy1kT25WY3gnms8+r5zpvE7g7XxgIXsCq3PMs+8H2R8IUO+JFfF15uThC8/fEc3y/nlRCCEuDofD4XA41cURtymEuAAAALAEhLg4HA6Hw+EU1y73j5dAiAsAAABLMK1p6eKgiDjztdd2hDorUsyObismiDphpRMPO+FidsTmTPKsiPvU41Zx3qtBpxNqqlOaR+fpKHgePU/dXmf6aHaork4jPqXrqOKDA6c0YVy9l2Tv562nel//9+Dv9t2//CIhbrd8SgESDofD4XC3s4sjblMIcQEAAGAJCHFxOBwOh8Mprl3uHy+BEBcAAACWYFrT0kVTERG7Nz357WicPRk0++vQZ01RzQ7H1OhWjfuyt9eJybK/sr7i6+4rJpI6IWT28XWiVjVGVmNVNTZ3trdiqnLFZO7se5hz380+RtnXb/a1kH0d3eo6P/ehT0ezHHH9HIpCCHFxOBwOh8OpLo64TSHEBQAAgCUgxMXhcDgcDqe4drl/vARCXAAAAFiCaU1LF/hExJmvvd4kmnK+pluNxGYFa7Mm4laEfBWvkR1uO9NRK+I+dTtmBY5qgOlMxJ0VpjpRunNdntJEXPU6d/ZB9qTb7Pg/+2+Ael+71fvun/7JI9Es7/dzFEKIi8PhcDgcTnVxxG0KIS4AAAAsASEuDofD4XA4xbXL/eMlEOICAADAEkxrWroQKCJid+2Fe6Jx9tRJNUJ14lwnrpq1Lk6cO3pedrRXMUk2+zXUaM/Zz6c0uVS9BrNDYXWfnlIYPYpGnXvYKZ0vFUG2es3M+hDDqX8Qwfl71O/n7/74zmiWI66fz1EIIS4Oh8PhcDjVxRG3KYS4AAAAsASEuDgcDofD4RTXLvePl0CICwAAAEswrWnpgqGIiN3l798fjdskgHMiNvU1sr86ftbkXHUfqM7Z3oqprNnTktX9nH2eVsS5zrpUxNLOMcq+54zuB872Zu97JwbNPq+yp5g7+9mJoLPPq+xjfqvh8Q9fui+a5f0+iEIIcXE4HA6Hw6kujrhNIcQFAACAJSDExeFwOBwOp7h2uX+8BEJcAAAAWIJpTUsX+ERE7B548OVo3A3DIjVecr6e3pk2OCuwzf5ZZ99XfJ38ipM3nXBbDe+cENKJyLPXZdZU1uzJ1xUx9+hns4Ni5wMLFcc8e9Ktcw+bde90jrmyr15+6YFolvf7KgohxMXhcDgcDqe6OOI2hRAXAAAAloAQF4fD4XA4nOLa5f7xEghxAQAAYAmmNS1d4BMRsbv3oUvRODu6vXj+mQN3+coTw+fNmiqaHck6UZyzLs5XuFfEfer0WzWAG51X6j5Vg7/sCZhqAKxeM6PfVxGHq0G7M4m3IrDNjkudqFU95rP2qfO6FWG+cw9zQuEZ984ffON90SzvXyMKIcTF4XA4HA6nujjiNoUQFwAAAJaAEBeHw+FwOJzi2uX+8RIIcQEAAGAJpjUtXYAUEbG7+I5XonH2BMdRNFUxVfTUJzg6YVv21M6K4NnZjtE+VcNUJ1pWJ5w6+0rdttHzsicFV4SQ2eeaesyd6dpO2KuGzOp945Qm2DpTx7Pvu9nnVcWHLG71Z3cPX41mef+8KIQQF4fD4XA4nOriiNsUQlwAAABYAkJcHA6Hw+FwimuX+8dLIMQFAACAJZjWtHShUkTE7qefOAxx3Zis4uvpKyLF0fOccMyZrJo98TM7HFN/n7P/nEhRnc6bHYc7MbcaOM6K0p3oVl1ndWqser1lT9LOnrY6a2Kv+vucWFV9XfVarQiU1aDYeZ7innrL09EsR1y//0UhhLg4HA6Hw+FUF0fcphDiAgAAwBIQ4uJwOBwOh1Ncu9w/XgIhLgAAACzBtKalC5Ai4sy0PTsmy46m1CjTmZiqrkv2ZNrssLLiK+tnTePMnnBaMRW4IsqsiHOzY2512qp6TY9+1plAfUr7L/uacaZXV0zNnnWtZq+LE0b3z/vYnZ+NZjni+rUQhRDi4nA4HA6HU10ccZtCiAsAAABLQIiLw+FwOBxOce1y/3gJhLgAAACwBNOali7wiYjYXXj+jmjc1GDSmbioThV1vm5cDdaywzsnFM6eAupEZ07cnB2mOmG0Mzk3e3JpxetW/L7s6cHOOVkxgbpiIq66zqf0gYWK+1r2JG1nIrOybd2HZfbrHIUQ4uJwOBwOh1NdHHGbQogLAAAAS0CIi8PhcDgcTnHtcv94CYS4AAAAsATTmpbuq94jInZ/+VdfjMad3PTM7LjPiShHEzorotaRq5hG7AST2V9Zr65zd47vLl95Ij0qzD6vsiehZgfo2RF+9vVbMUU6+0MC6mTf7LB8dH1UTBOvmASdHear913nQyDKfYMQV1s+pQAJh8PhcLjb2cURtymEuAAAALAEhLg4HA6Hw+EU1y73j5dAiAsAAABLMK1p6YKriIjde66dj8bZQdipT37NjjzVODc7rFTjr4qves+OCp3XdaJCJ8ZzolEn+FP3ffYxr4hfnddw7jnOVFv1GDnnqbOf1dcdbcfoHHfOodFrqPfTig+GjH6fcyxH26sE1F/7ld+OZnn/s1EIIS4Oh8PhcDjVxRG3KYS4AAAAsASEuDgcDofD4RTXLvePl0CICwAAAEswrWnp4qCIiN273vazaNwNg8mK6Z5O4JgdFarBVcXkTSfAzI5GKyZbZkePahSsRsvO/nMmiDrnZPb1URHsOoGteoxG51V2lD46Rs7kZnX6rXN9ZE99dq5fJ5p3jqWzzup9V1mXp97ydDTL+9eIQghxcTgcDofDqS6OuE0hxAUAAIAlIMTF4XA4HA6nuHa5f7wEQlwAAABYgmlNSxcRRUTsPvqjz0Xj7PjLCeWckLRi8mb2ZEYnyhzFfeq+Gq1fRaipxnPO6zrnkBP8OWGvE1bOimSzzxf1nHSmSM+aHqzGpc41kz1J2zlPnQ9eVEwTr/i7lRkyf+zOz0azHHH9fh+FEOLicDgcDodTXRxxm0KICwAAAEtAiIvD4XA4HE5x7XL/eAmEuAAAALAE05qWLkqKiNg98s0vRePskCr7q9krJuI64bETl2bHfdlTLNXXVQNRZ7psdviZPcHW2d6KCdTO/htth7P/KuJS9Z5zSlO4nUnBzv5ztjf7fuqcLyPn3K+cv4POfbc/T//8jkvRLEfE/5+gHIUQ4uJwOBwOh1NdHHGbQogLAAAAS0CIi8PhcDgcTnHtcv94CYS4AAAAsATTmpYu+omI2F14/o5onD0J0Plq9tHPVgRw2WHbrLjPieec6DE7qFPX2Tm+2ZNL1XPIOTecCcpqOOucG05kXBHdju45zoTYWXGuGo1mX4OzJi1nR9rZ+zl7ynV/Pn/n3JVoliOCibij5VMKkHA4HA6Hu51dHHGbQogLAAAAS0CIi8PhcDgcTnHtcv94CYS4AAAAsARLhrgVE3FHP1sRsTlxrrq92duWPWF3VhCbPcnY2Y5RqKnGeE4Y6EStTkjq7PuKINsJ30frp07hdo5R9n5W90F2lK4+L/sekf1BCSekr7iHKefu9X8P/m5/6tInCXG75VMKkHA4HA6Hu51dHHGbQogLAAAAS0CIi8PhcDgcTnHtcv94CYS4AAAAsATTmpYu+omI2D3yzS9F425q6p8aoqkRmxpSZU/jVGOy0fPUyFiN57Lj3Iqvjq+YVqtOJM2eLutMus2e3qqGpM61oE4KVtd51jEfHQ81unXOq1kTcSteQ73vqvcS9bxyAmD1Xpf9gQD1fFb23zd/9zeiWd7v+yiEEBeHw+FwOJzq4ojbFEJcAAAAWAJCXBwOh8PhcIprl/vHSyDEBQAAgCWY1rR00VlExO6jP/pcNG6TULNi2mpFIOXElrNCPjVsmzXB1jke2dNls/eLMxHXiUa3ntB5M0Hx6HnZcbi6D0bboYak2ddCdkjqxObOeaBOX54VI2d/qMT5u6UGwP3rvvrifdEs79cvCiHExeFwOBwOp7o44jaFEBcAAACWgBAXh8PhcDic4trl/vESCHEBAABgCaY1LV0UFxGx+865K9G4G4aaTkiVPW3VCa6yf9+sYG0Ujs2aUloRxY3232h7nX0/K+7LniydfV6pv08N0J2YUQ391XPIibQr9qkTqldE2tkBa8XfAPXccIJsdZ0V95Ovvzea5f12RCGEuDgcDofD4VQXR9ymEOICAADAEhDi4nA4HA6HU1y73D9eAiEuAAAALMHJT8R1pq1mT8CsmGqbHdmNtsMJ/pzpvBURW/brOlNZ1X2fPdlX3TZnmmlFbOlElE4EnT0hWz0PKibJqpFn9jUzawr3KX2gwrl3VoTHirvr2fujWd7/viiEEBeHw+FwOJzq4ojbFEJcAAAAWAJCXBwOh8PhcIprl/vHSyDEBQAAgCWY1rR0gU9EnIl8bhhhqXGVGmWqwZUaNGXHgtnrPGsCZnYUN9pXoyA2O3gercvoWGYH3o5ztq0iXlfXpWJiavbxcMJoNYYfnX+qc+4v6rpUHI/s46Zub8Vk84roVtl/F56/I5rl/fOiEEJcHA6Hw+FwqosjblMIcQEAAGAJCHFxOBwOh8Mprl3uHy+BEBcAAACWYFrT0gVDERG7H3zjfdG4TaYmqoFUxdfYq/FXdgjpbEd2tOxMsB097+L5Zw7c5StPyBGb+roV8ZwTzqrblj351Qnk1enVzuTm7Mgz+9rK3n8VU8JH+3T0urOuhex9nz39O/semx2+96/7pie/Hc3y/mejEEJcHA6Hw+FwqosjblMIcQEAAGAJCHFxOBwOh8Mprl3uHy+BEBcAAACWYFrT0oVZERG7l196IBp3UzFjdnSrfo19RWDmTGGseJ6zbaMwMDtsyz5fZp0H6jo753PFxF51QnH2McqO9bNfNzvOVaPR7OMxeo1ZAXpF1Dq6h2VH7hXRvHJfu/ehS9Es758XhRDi4nA4HA6HU10ccZtCiAsAAABLQIiLw+FwOBxOce1y/3gJhLgAAACwBNOali7+iojY/fCl+6Jxu09d+uRwwulo4qITa6nBmho4quGsEy5mx7nZX3OuBmvqsXQmAGd/jb0asWW/bnaQWBHdVkz2zT431OtDvS7VdXGuwdG6jH42+14ya5q4GsM7k6rVD0VU7NPsD1Tc6gdSPvRbvx/N8v5noxBCXBwOh8PhcKqLI25TCHEBAABgCQhxcTgcDofDKa5d7h8vgRAXAAAAluCkJuJ+8au/Fo2zQ001mnK+6l2dMJk9hVENx05pouto/41C6+zgdNbvy57y6kSt2dNWKybJjs7J7ADd2Q7nvqFGns6EXXWfZgftzjqrx9c5h7Lvu07YO2s7Rs9T/ja+/cGzE3E//K3PEOJ2y6cUIOFwOBwOdzu7OOI2hRAXAAAAloAQF4fD4XA4nOLa5f7xEghxAQAAYAmmNS1dbBQRsXvTk9+Oxm3y9fROPDeKRkc/q07TVWOt7IAwe3Kk8xXu2RMwsyPA0X5xwk9nP1dMsB2d9+pxc6JCNZB3po86+6/ivlER8FdMaZ51vWVPfXam1Vbcn9W/H5kfPnnPtfPRLO+fF4UQ4uJwOBwOh1NdHHGbQogLAAAAS0CIi8PhcDgcTnHtcv94CYS4AAAAsATTmpYuGIqI2F14/o5o3CZTLGdNxJ0VW45ew4keK0I+NehUt8OJc9VzwwngTul8GUWjzkRc59oaPS87Is+ePDxaZzXWdyJjJ/quCHad+No5/5z7UPbfnuz94py7o9dV9v31fw/+bn/q0icJcbvlUwqQcDgcDoe7nV0ccZtCiAsAAABLQIiLw+FwOBxOce1y/3gJhLgAAACwBNOali4YiojYXXvhnmjcJtML1aBz9Dx1iuWsCNCZGutsmxqdOcdIDcdGQWf217qrQbY6FViNh7OncVZMUXWmxqpx7qz950z7HT3PubacczI7LnX2s3PMne3Ivhay72HOeZ85rfvllx6IZnn/vCiEEBeHw+FwOJzq4ojbFEJcAAAAWAJCXBwOh8PhcIprl/vHSyDEBQAAgCWY1rR0MVRExO7y9++Pxm0Szzmxlho+qV8ZfkoTJp1pv06Mp+6D7Mm+6mRQZyJuZgB3o/VTA1s1TB2tixNlOnGuM0m2IrbMjuGdiHIUMmcHts62ZR8P5/pw1s9ZZ/Veokbp2ROPlWP56ov3RbO8f14UQoiLw+FwOBxOdXHEbQohLgAAACwBIS4Oh8PhcDjFtcv94yUQ4gIAAMASTGtauigpImL3wceei8bdMOpyYignnnMmxFZEgGro5UwkdSYAVwSO2VFr9jE6pRh0tK+yJ846k3PV6NaZfqvuP+d4qE49D07p2lKPUcX0ZXV71WnEznk1Whd1/zkfilC341Ynr3cfltn/viiEEBeHw+FwOJzq4ojbFEJcAAAAWAJCXBwOh8PhcIprl/vHSyDEBQAAgCWY1rR0sVFExO49185H43ZfuPZxK1iriNNmBZ0VsaUTR86Kc51QLvtnK9YvO6JUJ9M6saUaRzrhuzPRVd1X2SGpuu8rri11H6jnxmidK6ZhZwfe2QGwen927rGZU+WvvXBPNMv73xeFEOLicDgcDodTXRxxm0KICwAAAEtAiIvD4XA4HE5x7XL/eAmEuAAAALAE05qWbgJrRMTuA1cPvvZ690c/f1z+avFTn2aqxl9OyOcEa2rodepTgSvCWXXapRNMVkS3o+BvNBnZOXcrJpw618do/2UHner9YFb4PnLOtT963ewJytkTe51QOPt+5YTCzgTq0bXf/+yF5++IZnn/GlEIIS4Oh8PhcDjVxRG3KYS4AAAAsASEuDgcDofD4RTXLvePl0CICwAAAEtw8hNxTyn8zJ6yWTExNTuoyw7+1CmgzjFyotZRFKc+zwnlRs9zgmJ18uboeKjr4kyqds777Oty5NTzVJ2cO9qOU582rX4oQr0Xn9Lk8OxQ2Dl3s4+5Gn2PzvH+Zz/6o89Fs7x/jSiEEBeHw+FwOJzq4ojbFEJcAAAAWAJCXBwOh8PhcIprl/vHSyDEBQAAgCWY1rR0sVFExO6HLx1OxL1RiOZ81XZ2HKlGhaOQygkXs+Ncdb9kTykdTWGsiHOdSajq+jnnS0V87UzenDWNODtwdCYFO+epetwqolFneqtzL1avS/XeWTH5Wv0wgTJdduY6q+d9f8088ODL0Szvj0cUQoiLw+FwOBxOdXHEbQohLgAAACwBIS4Oh8PhcDjFtcv94yUQ4gIAAMASTGtaurAoImL36ouHIe7NBKIVX1/uxILO1NPsODd7O1SnTmXN3jZnn6qRXfY0UydCVeO+7HNc3TZ1Yu/o92WH4Oo+GJ0HFdNbR89zrtXR9jpxc3YErU4ozo5zswNWJWqt2vfOJOj+nLz4jleiWd7vvyiEEBeHw+FwOJzq4ojbFEJcAAAAWAJCXBwOh8PhcIprl/vHSyDEBQAAgCWY1rR0EVFExG738NVonB0ljYIrNaQ6pSmgTnBVMcXSmdTq/D71NdSfzZ54POv4qpOgncnI2RNnR89Ttzc7Hs4+752I0pkk6xyP0Wuozxsdj+xp09n3K+eaGW3byDnnqRqCq39nbvV+de7dr0WzvN9/UQghLg6Hw+FwONXFEbcphLgAAACwBIS4OBwOh8PhFNcu94+XQIgLAAAASzCtaekiooiI3YXn74jG3VScVjGZsSLOzd6OiomLqnO+7l6NybKn0DrRXvbkUuf4zvq6+9F2qLGlM2XY2Q4nyFbPUye2dO4Ravw6mvqsrp8TBVdMGc6euD3aNvX8U4+lGiM7r6Gsc/dhmf3PRiGEuDgcDofD4VQXR9ymEOICAADAEhDi4nA4HA6HU1y73D9eAiEuAAAALMFJTcS99sI90Th7SqkzCdCZDFrx1eLqhEn169Czo8yKabpOVF0R5zr7arQdzrRQNdBzwuPsyFONc7OPUfbU4uzJtNkBvzNxNnuaszMN24mgnbA3e7K0+jw1jM48ll/7ld+OZjni+rkbhRDi4nA4HA6HU10ccZtCiAsAAABLQIiLw+FwOBxOce1y/3gJhLgAAACwBNOali5UinjjHdvrzg4hs6fVVoR86sRFNXpUvzZdjcmyJ+I6k27VmNH5mviKKE59DSdgdY6beozUc01dZzWgrpiiWhHnOlNZs6P57Hts9vHIjvCz7+3qfnEmaav3A3W/KMftqbc8Hc1yxPVrNQohxMXhcNovfW8AAB5CSURBVDgcDqe6OOI2hRAXAAAAloAQF4fD4XA4nOLa5f7xEghxAQAAYAmmNS1dDBXxxju2191NTSnNnkhaES46EdsornJCTScMrAjvRvtPjZGzI091ymtFuK2Gx6PtVUNrNQYdvYYakTvrnD0hdnQeVEy5du4l2fdJZwqtc1+riPCz41znnp09TVfdf7f6uruHr0azvH/dKIQQF4fD4XA4nOriiNsUQlwAAABYAkJcHA6Hw+FwimuX+8dLIMQFAACAJZjWtHRxUMQb79hed2WBVEU0mhlDufHrKAibNSl41kTc7ABTnYhbMYFVjaqzp02P9l/2tTArWq5Yv9HzRi57UnD2dZkdLavXuTMd2pn+rU6HVs8/5xocbYfqlO392J2fjWZ5v5+jEEJcHA6Hw+FwqosjblMIcQEAAGAJCHFxOBwOh8Mprl3uHy+BEBcAAACWYFrT0gVIERG77/74zmjcDaOp7EDUiVorvjbdiXPVYDI70DuluHlWWFkReTrn7imF6tn73pkOrQbZ2dGtuv+yQ/rsGN6ZZKyG5Wro6lwL2cfICYWdcDvzb+NTb3k6muX9OkchhLg4HA6Hw+FUF0fcphDiAgAAwBIQ4uJwOBwOh1Ncu9w/XgIhLgAAACzBSU3EfeSbX4rG2TFodtynxqqn9LXp6v7L/n3ZwbMaFKvHtyL8dI65ExA6+8WJc9Xrw4kKKyLj7HNDnZasxq/qeZX9AYNTityz74nZv089buq1n+1uNSzfPXw1muX974tCCHFxOBwOh8OpLo64TSHEBQAAgCUgxMXhcDgcDqe4drl/vARCXAAAAFiCaU1LFwdFROwuPH9HNO6mgsmK6bLOlMhRqDl6nrrOsyahOq8xChJH+8WJBbOnlKqTbkfb5kwLVddFjfuyw+hZ55oags86N5xzfNZ0bWc/j56nOmeqd/Z2OB+AUP9WOB+AmDXZt3/euXe/Fs3y/vdFIYS4OBwOh8PhVBdH3KYQ4gIAAMASEOLicDgcDodTXLvcP14CIS4AAAAswbSmpYuDIiJ21164Jxp3w0mFFXGaE2WO4kj1a8mdr52viHOdn3X2ixrZZceg6rmmBrGnNC00c1LmjZ6XHdie0lTWiomkamQ82l7nGDnnwazQ35l+mz2dvGLquHM8bvXa6qbW739fFEKIi8PhcDgcTnVxxG0KIS4AAAAsASEuDofD4XA4xbXL/eMlEOICAADAEpx8iKuGaBXRrTOx0vlacieKyw7q1IDLiTKzY2QnFnSO+SnFoE6kqEaF6nlQMYF65Jxz0tnP2VHmqUf4ownAzocOsgNW9byaNTFaXWf1XuLc6/rndVPr99sRhRDi4nA4HA6HU10ccZtCiAsAAABLQIiLw+FwOBxOce1y/3gJhLgAAACwBNOali7wiYgzkc8N4yonIMye2unEgup0xexJmRXTFZ14riI4zY5VR8c8+/xz4j5nO9RzqGIfZE9VVuP1zKmiN9ovs2LzCufE9dkTcbOj6uyJ0ac09bl/Xvdhmf3vi0IIcXE4HA6Hw6kujrhNIcQFAACAJSDExeFwOBwOp7h2uX+8BEJcAAAAWIJpTUsXEUVE7O77vT+Mxt1UmFUxabQi1FRfd/QaTsCVva/UgNoJNZ2pmOoETGfK5imdf6N1VifJqqHrrKnAowmso3VWY1pnXdQIX70usyfnzpq+7Nw3nCnmFdN0nXBb/VnnHMo8x//6yV+NZnn/+6IQQlwcDofD4XCqiyNuUwhxAQAAYAkIcXE4HA6HwymuXe4fL4EQFwAAAJZgWtPSRUQREbs/+IuHonE3nGKpBlxq5JQ9UdOJI50JiWoc6QRcTjw3ep4TLs6aiumEwrOi79E+VafLqqHr6Pc5wakzEdcJ2rPD1NE6Z0+RVq+3rSemutebE7lnx8POtZUdyap/F7Kj5X47Hnjw5WiW968bhRDi4nA4HA6HU10ccZtCiAsAAABLQIiLw+FwOBxOce1y/3gJhLgAAACwBNOali5eiojYffW1X4rG2UGsGt06oWFFgKlGt87k0uxAT53YqwaiTpx7SoGt45ypndkxtxPdzppe7UxGdkJw9Vg62+FMw6649tXJ5uq+V0Pm7Lg5+37lBM9OxHur1/7bH7wUzfL+Z6MQQlwcDofD4XCqiyNuUwhxAQAAYAkIcXE4HA6HwymuXe4fL4EQFwAAAJZgWtPSRaMREbs/v+Mg8ikLYp2AtSIGdSaXqhGbE945k26zg2L1eDhRq/P7nPPUOR7qxE91n86aAupcR845OXqeE9hWTEF2tkM919TzoOKekz3FV70fjPZBRfDs/Ozo3Bj9/eif9663/Sya5f32RiGEuDgcDofD4VQXR9ymEOICAADAEhDi4nA4HA6HU1y73D9eAiEuAAAALMG0pqULfCIidt85dyUad1MB5ihKckIlNVhTQ031dSumMGbHX86ETnU6r/r7KgJHZ51Hz8v+OnnnXFMjSic0PKWIclZk7BwjZzucicxKqHkzk5ErJks7H5SYNXk4+zpXzw3lAxAfef+L0Szvf18UQoiLw+FwOBxOdXHEbQohLgAAACwBIS4Oh8PhcDjFtcv94yUQ4gIAAMASTGtaugApImJ3+fv3R+NuGKE64ZMTdf1tiQWzYzf1a9PVGC97uqyzfs4U3+xjlB3nqvtZ3X/Z6+zsF2cqsBoPz5rsq5672R9EGJ3j2ZF29nRo5/7n7Pvsc6MizlU+BPLBx56LZnm/vVEIIS4Oh8PhcDjVxRG3KYS4AAAAsASEuDgcDofD4RTXLvePl0CICwAAAEswrWnpop+IiN2rL94Xjdtk2mB2TJsdCzqhV8V+cabQZsebFcGzGt2OpoWOnjfrfHb2nxopVkx5rbjenH0wep46NTY7fHdiVfVaUINO53xRr4/siNcJvLNf14mHnWnnves+LLPf91EIIS4Oh8PhcDjVxRG3KYS4AAAAsASEuDgcDofD4RTXLvePl0CICwAAAEswrWnp4qCIiN1Pvv7eaNxNxaUVQeLoeWqwVhELZk9hVCfEqlFhdiTmRGzZ02DV82C0/9T94sTS2VN3K6Lb0b5yznFnn57StN/s60M9RhWxqjNFOvvazz7mFR8myJ6M3Lu7nj0b4r758T8jxO2WTylAwuFwOBzudnZxxG0KIS4AAAAsASEuDofD4XA4xbXL/eMlEOICAADAEpxUiHvthXuicTf8SvgVJ6ZWhG3ZE1NHUZy6vdmRZ8VXvTvrp4ako+c5x2i077MD1uxwWz2H1JDZCY+zXfY16ESjFYG849Rzwzmfnam7o9etOF+cv0fqtXCr4fbu4avRLO+fF4UQ4uJwOBwOh1NdHHGbQogLAAAAS0CIi8PhcDgcTnHtcv94CYS4AAAAsATTmpYu+ol44x3b6+6m4kg1OnOmYo4CKScmc76ePnt6pjOxUt1eJ7pVQ1d1GnG2U0M5NaJUt+3i+WcO3OUrT8gxnhPnqueQGt2OtqMiznWma4/WJTsQzZ4ofErTjUc/qx5zJ2jP/pDFrA+BZP/tUc7dc+9+LZrl/bZFIYS4OBwOh8PhVBdH3KYQ4gIAAMASEOLicDgcDodTXLvcP14CIS4AAAAswcmHuGoA54Su2YHUKMJSv0Z8tH6jSNEJTrO/hj17Sql6PG51quPNTPd0grrR89R1Vs81dSKuczzU89mJBZ2Jwk5k7ES36uTr7ODZ+UCAGpKOXjf7mKtB9uhnnYnHzv5Tg3Hn/jJav5FT96kaQSvn5PV/D/5uMxH37PIpBUg4HA6Hw93OLo64TSHEBQAAgCUgxMXhcDgcDqe4drl/vARCXAAAAFiCkwpxLzx/RzRud+7R76UHehXTC52vk8/+Sng1olT3gTMhNjt+dX7WOV+c4+ZMH3XOA+f8c8JjZzqqM1HYiW6zJ0E79yEn0lanvDqBqLptM6a33swxd/7OZJ8HTgTtrIuy/3YPX41meb/OUQghLg6Hw+FwONXFEbcphLgAAACwBIS4OBwOh8PhFNcu94+XQIgLAAAASzCtaekCn4iI3bUX7onG3VT8mh1mZceg2dHj6GezI9SKfZodGVdMEFVjRnUis+rUfT9rPzuTc7PXr+L3jc6hin3qxPDO5PCK/efcm7LvQ+q6OOucPSXXCZmV/XL934O/24S4Z5dPKUDC4XA4HO52dnHEbQohLgAAACwBIS4Oh8PhcDjFtcv94yUQ4gIAAMASnPxEXCdMnTXNtGLqrjNpNDuAc0JSJ/hTJ7+OtkONbtWvoneme2bHeNlBZ8Vk3+xJvNkxfEVcXxHDO9Gt+hrO+jn7IPsDFc59LXsCurpPnantyvoxEVdbPqUACYfD4XC429nFEbcphLgAAACwBIS4OBwOh8PhFNcu94+XQIgLAAAASzCtaemCoYiI3Zue/HY0zp4Mmh3KVcSlTnTrTHQd/b7sCLAiGj2l/Zc9yVONpbPj5orIXf3Z7Dh8VjysnqdO4K2eG8507VnXuROwOutX8WGMiu291Xv2xXe8Es3y/jWiEEJcHA6Hw+FwqosjblMIcQEAAGAJCHFxOBwOh8Mprl3uHy+BEBcAAACWYFrT0oVjERG7H750XzTuhnGkE9mpUaYzITZ7WqMTjaoTXbMn4o6elx2SOiGkE+dWTBRW168iIMyOKNWprBUTcUc/6xw3J0pXr+ns6NaJhyvunc40XXVd1PPA+eCFcz5XxL6j7e3dvQ9dimZ5v85RCCEuDofD4XA41cURtymEuAAAALAEhLg4HA6Hw+EU1y73j5dAiAsAAABLMK1p6QKfiIjdyy89EI27qUjWieLU3+dMFpw1PfOUJuJmT4jNDiHV+NU5vur6qVHwaJ2z4/Ds8yB7nzohpBNvZse5TgheMUU1e2Kvs87q8VDXJXudnXu7ep07H2K41X3w2O98OZrl/fZGIYS4OBwOh8PhVBdH3KYQ4gIAAMASEOLicDgcDodTXLvcP14CIS4AAAAswbSmpYuSIiJ2f+ef/q9o3E0FmNmTPCsmfjoBlxqwOqFcxdfJVwSdTnDqnH+qU2NQZ/poxYRYNZJVg/Hs61I9/7KjW+ccz76XOOeaM7G3YtKts84VQbG6HaP1y14XNQpmIu4hfcBzo+VTCpBwOBwOh7udXRxxm0KICwAAAEtAiIvD4XA4HE5x7XL/eAmEuAAAALAE05qWLsyKiNj99BOvROPsoE4NWLMnfjpBpxNMqrFqRbyZHTyrkd3F888cuMtXnpAjz4rwMzvOrYhGs6+P0fFwjlv2Maq4H1TcXyriXOeczI5z1QmxanTr3BOdKbQVk69vdTJy92GZ/T6IQghxcTgcDofDqS6OuE0hxAUAAIAlIMTF4XA4HA6nuHa5f7wEQlwAAABYgmlNSxc0RUTsdg9fjcZt8rXfs8IxdTuyI8/seLMi1HSOpTpNsuIYqfvAiQ9nnafZAbV63Jxz1zlus6Y+Z0/szZ7eWnFOqvcc5z6ZPfXZuT9n77/Mc7f7sMz+Z6MQQlwcDofD4XCqiyNuUwhxAQAAYAkIcXE4HA6HwymuXe4fL4EQFwAAAJZgWtPShUAREbun3vJ0NG73hWsfTw81KybEZges6leQqxMhs2PBiohNPeaj56n7Tz1GpxTOZk+mzY4ZZ00BrZhknB2DqpNa1ftV9hRk9TVmHY/sdXHuVxWvmx3dKs/rPiyzf14UQoiLw+FwOBxOdXHEbQohLgAAACwBIS4Oh8PhcDjFtcv94yUQ4gIAAMASTGtautArImL39f/0kWicPblUjTJPKSR1wkU12nMCrtHvU/dpxVTMimM0ck4wWRGXjqLbi+efOXCXrzwhx7mqU+NN9WcrJoOq0a0TxDr7IHu/VETfIzc6/0b3tey/AbPOF+fDHU4YnRkKP/ehT0ezHHH9vI9CCHFxOBwOh8OpLo64TSHEBQAAgCUgxMXhcDgcDqe4drl/vARCXAAAAFiCaU1LFxFFROxefumBaJwdtp1SrKUGomp0Owom1YjNiZadALhiGmz2McoOo2dNCx2tX3bM7YSfFXFkdvSo7qvR87LXL/uaca6t7A8EZE/xzb5vqOs32gfO/UCdyKz+7Oi49bH0v7/770azvH9eFEKIi8PhcDgcTnVxxG0KIS4AAAAsASEuDofD4XA4xbXL/eMlEOICAADAEkxrWro4KCJid+k3fz0ad8NpoRVfI17xtenqz6rhZ3bs5gTAo3XOnhY6K+RT94s6cVY9ltmTULOj29G6OJOMs69L55irUbpzfVRE2ur2jp6X/YGA7HuxetyckDk75nZiWmef3up97T3XzkezvH9eFEKIi8PhcDgcTnVxxG0KIS4AAAAsASEuDofD4XA4xbXL/eMlEOICAADAEkxrWrpgKCJit3v4ajTODsycONd53YqJrk4w6Uz8HMWlo3XJnv6YPVk1O8pUw2NnCq0T8qnbocbS2fvPCXYrjvnoGDnxtXossz9gkB0Zq9s76xpUrzdnnzpxbvY90bn2lf33kfe/GM3y/mejEEJcHA6Hw+FwqosjblMIcQEAAGAJCHFxOBwOh8Mprl3uHy+BEBcAAACWYFrT0gVIERG7c+9+LRq3ybTBirBNfV52mOVEtxUTPyuOhxqmVkxMVYNOJ7p1IsDs80+N+5x9kB0uquefen0406tP6dxVj1v2tG71Hqauc/aEYucaXHH6d79fPvjYc9Es758XhRDi4nA4HA6HU10ccZtCiAsAAABLQIiLw+FwOBxOce1y/3gJhLgAAACwBNOali4EiojYXXzHK9G4m4rsRiGVE7Cq8dIoCBv9PjU0zJ4QO9oONRR2Jkxmb68TkzmB46xg1/nK+uxJnuprODF39r7KDtqdSdXZ4bGzztnnqTMd2jke6r1Jndir3tcqzj9nYm/2udFvx+Xv3x/N8v55UQghLg6Hw+FwONXFEbcphLgAAACwBIS4OBwOh8PhFNcu94+XQIgLAAAASzCtaekCroiI3WO/8+Vo3E0FcKNAKnvaYPbXoatTMdUYLzvWGq1L9kTX7CC2InStCIUrwtnssFeNbkdxpHPNOPvK2ffOpODsicfOsXTO04qAOvte7MTSFdHtaJ2z/y7c6j3irmfPhrhvfvzPCHG75VMKkHA4HA6Hu51dHHGbQogLAAAAS0CIi8PhcDgcTnHtcv94CYS4AAAAsATTmpYuzIqI2N370KVoXNlkSydOG8Vao/hw1nRPJ6xUt3e0zk445kwjdkLhWV8JP2t6q+Oc46HGkaNjlB2wOtegc9yyf58Tuauv65zPzv1gtC7Z03krotvsdc6+DynX/oXn74hmef8aUQghLg6Hw+FwONXFEbcphLgAAACwBIS4OBwOh8PhFNcu94+XQIgLAAAASzCtaelCoIiI3Zue/HY07qZCw+z4Sw2uRoFUdkiVPXnTiS1Hvy97gmh2KOysc0WcWzG9NXtyrhNLV4TCTgg5K5p3pqiqIems89mJbiumTVfcI9R1dv4eqT+r/t3qP1Ty1Z89Fc3y/jWiEEJcHA6Hw+FwqosjblMIcQEAAGAJCHFxOBwOh8Mprl3uHy+BEBcAAACWYFrT0kVEERG73cNXo3H2hE41NnKC2OxJj+r2jqaKqtt2ShNdVZc9jXPWxNnsqDr7/MsO/iqCTmefqhN71Qm2s+JhNegcTeu+eP6ZA3f5yhPp53PFBG9nknFFtFzxs+rvG53jyrnxgav3RbO8f14UQoiLw+FwOBxOdXHEbQohLgAAACwBIS4Oh8PhcDjFtcv94yUQ4gIAAMASTGtaumAo4o13bK+7mwpi1al/owDJCdGy48MVoy4n1MwO+SqCv+x9qkbVToDpxIyzIsqKKbSja38UJFZcb85+qZiMrN6v1CnIo33vTFB2JnM7+089Tyvuk6pT7y/987774zujWY64fr+KQghxcTgcDofDqS6OuE0hxAUAAIAlIMTF4XA4HA6nuHa5f7wEQlwAAABYgpOfiOvEaeoEzFF4lx2NOtGjE3XNmmCrbsfoGGVPp1SjMycGVfefev45E48r4tfsc0O9fkfrUnGOn1Jsnn08sqfzqlOGK877U9oH2VOuK/4e9ev38ksPRLO8344ohBAXh8PhcDic6uKI2xRCXAAAAFgCQlwcDofD4XCKa5f7x0sgxAUAAIAlmNa0dCFQRMTum7/7G9G4G05DVCNPNZoaxV9q7FYRPY6e53xl/ayQb7RfRsGaE52pxzc7AnSivdHrZh9L9evp1YmkzqRR9bidUsyoTtx2jkd23Jz9PPW+5hxLJ5qvuF854fEpnc/q9vbuB994XzTL+/WLQghxcTgcDofDqS6OuE0hxAUAAIAlIMTF4XA4HA6nuHa5f7wEQlwAAABYgmlNSxcWRUTs/u0z/ywad1MhpBqJqRHgKIbKDuXUKDM7YlO/2r4i+MueDDra3lF0e/H8Mwfu8pUnhs/LXr/Rvh8dt+xJvOp5XxGbZ8eMaqToBInZ4buzfs7xmDVZVd1X6rTkiu1Qr1U19B/dcyqu88w4/K5n749meb+vohBCXBwOh8PhcKqLI25TCHEBAABgCQhxcTgcDofDKa5d7h8vgRAXAAAAlmBa09IFSBFx5muvdx/+1mesWLAiEM2eEulEo07E60wGdZ6XHTc7oZxzzLPj3FMKNZ3ryIlaR8fSmbo7a8prxYRTZ+L2rDjX+dns+4YTkavBrnrPzr7nZE5Fv/D8HdEs7183CiHExeFwOBwOp7o44jaFEBcAAACWgBAXh8PhcDic4trl/vESCHEBAABgCU5qIu6rL94XjdtkCm3213k7UZczBXT0PCewdQIuJ7ybNXF2tP+cCZPOeZUdks66Fiqix+wo2IlunfDdia+ddamYwDr62eyw14lQs19XPXezzxd1ezMj8vt+7w+jWd6/bhRCiIvD4XA4HE51ccRtCiEuAAAALAEhLg6Hw+FwOMW1y/3jJRDiAgAAwBJMa1q6yCkiYvfTT7wSjbupqEsN6kbPy/zq7i2Cv1lTNrODP2cqqxPeOUGnGo1WTGRWo+DsdXGuN+f6cCZ5OrGvM1XZiW6d816dtqreEysmWjthr3NeVUx9zr63z7qP9+v3Dz78XDTL+30ahRDi4nA4HA6HU10ccZtCiAsAAABLQIiLw+FwOBxOce1y/3gJhLgAAACwBNOali4YiogzX3t9w6mE6tfdn1LUqq7zKX21vRofZk96dOK57KBz5P62nEPZk6Wd6aPqa8zafxUTcZ2o+uL5Zw7c5StPDK9VNZqvmMhcEexmT312IvJZ943M+/jbH7wUzfJ+O6IQQlwcDofD4XCqiyNuUwhxAQAAYAkIcXE4HA6HwymuXe4fL4EQFwAAAJZgWtPSRUQREbtrL9wTjbMjJzWUyw7+siOxiom9ash8StGtE8o5x0j9fc52ZE90VY9HdpSZfc1UhJrqPnBiS/Wekx1WqhNxsyPU7HMj+xyqmJyrvoazr9T7gRpu9+fQu972s2iW978vCiHExeFwOBwOp7o44jaFEBcAAACWgBAXh8PhcDic4trl/vESpv330O7hq2fchXjtjLv4jlfOuJdfeuBg+Z0R8Qd/8dCB+3BEfOfclTM/+90f33nGnXv32de9/o3Te+4erPPdEfG1X/ntA/epSxFPveXpA/eFa+OfHb3GiNE+GDF6jRGj7b33oUtn3PVBQgdc/z/NAz742HOd+WQ88ODL0muMtm20X/7e3f/ywF2+EvGxOz974P7o5+OfHb3G9UGGb/DoeP+N9pX6+0brctez9x8+7/GIv37yVw/d5yNeffG+A/XWGJ/3P3zp8HkR43P83/33w9f9cER88au/duC+8JXxa1z6zV8/3I6v6Ofz6Hmj62N0Hak/q54b6vH9+//kf5xxo+M7Op8/9Fu/35nPDJ83YrQuI0bX4HuunT/jPvL+F8+40XX5pie/fcaNtneEep7+4BvvO3SPR/zk6+89/NlHx9eHui4jRsd8dD7/zz9+5MB9OCL+yxP/4sB96nH93j5a59F9w/nb89yHPn24zt/S7wcfuHr2eco5NDp/Ij45cNtB04LD4XA4HE51ccRtCk0LAAAALAFvWgAAAGAJCHFxOBwOh8Mprl3uHy+BibgAAACwBPz3EAAAACwBb1oAAABgCXjTAgAAAEvAmxYAAABYAt60AAAAwBLwpgUAAACWgDctAAAAsAS8aQEAAIAl4E0LAAAALAFvWgAAAGAJeNMCAAAAS8CbFgAAAFgC3rQAAADAEvCmBQAAAJaANy0AAACwBLxpAQAAgCXgTQsAAAAsAW9aAAAAYAl40wIAAABLwJsWAAAAWALetAAAAMAS8KYFAAAAloA3LQAAALAEvGkBAACAJeBNCwAAACwBb1oAAABgCXjTAgAAAEvAmxYAAABYgv8HOgP7ojxK8NgAAAAASUVORK5CYII=\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): ???" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.6" } }, "nbformat": 4, "nbformat_minor": 4 }