Add files via upload
This commit is contained in:
parent
7d95a83192
commit
994d790bf9
844
ipynb/Advent-2020.ipynb
Normal file
844
ipynb/Advent-2020.ipynb
Normal file
@ -0,0 +1,844 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"<div style=\"text-align: right\" align=\"right\"><i>Peter Norvig<br>December 2020</i></div>\n",
|
||||||
|
"\n",
|
||||||
|
"# Advent of Code 2020\n",
|
||||||
|
"\n",
|
||||||
|
"This year I return to [Advent of Code](https://adventofcode.com), as I did in [2016](Advent+of+Code), [17](Advent+2017), and [18](Advent-2018.ipynb). Thank you, [Eric Wastl](http://was.tl/)! This notebook describes each day's puzzle only briefly; you'll have to look at the [Advent of Code website](https://adventofcode.com/2020) if you want the full details. Each puzzle has a part 1 and a part 2.\n",
|
||||||
|
"\n",
|
||||||
|
"For each day from 1 to 25, I'll write **four pieces code** with the following format (and perhaps some auxiliary code). For example, on day 3:\n",
|
||||||
|
"- `in3: List[str] = data(3)`: the day's input data, parsed into an appropriate form (here, a list of string lines).\n",
|
||||||
|
"- `def day3_1(nums): ... `: a function that takes the day's data as input and returns the answer for part 1.\n",
|
||||||
|
"- `def day3_2(nums): ... `: a function that takes the day's data ass input and returns the answer for part 2.\n",
|
||||||
|
"- `do(3, 167, 736527114) `: checks that `day3_1(in3) == 167` and `day3_2(in3) == 736527114`.\n",
|
||||||
|
"\n",
|
||||||
|
"(During development, I can say just `do(3)` to see the output without checking it.)\n",
|
||||||
|
"\n",
|
||||||
|
"# Day 0: Imports and Utility Functions\n",
|
||||||
|
"\n",
|
||||||
|
"Before Day 1 I prepared (a) some imports, (b) a way to read the day's data file and to print the output, and (c) some some useful utilities:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"import re\n",
|
||||||
|
"from collections import Counter, defaultdict, namedtuple, deque\n",
|
||||||
|
"from itertools import permutations, combinations, cycle, islice\n",
|
||||||
|
"from functools import lru_cache\n",
|
||||||
|
"from typing import Dict, Tuple, Set, List, Iterator, Optional\n",
|
||||||
|
"from sys import maxsize"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def data(day: int, parser=str, sep='\\n') -> list:\n",
|
||||||
|
" \"Split the day's input file into sections separated by `sep`, and apply `parser` to each.\"\n",
|
||||||
|
" sections = open(f'data/advent2020/input{day}.txt').read().rstrip().split(sep)\n",
|
||||||
|
" return [parser(section) for section in sections]\n",
|
||||||
|
" \n",
|
||||||
|
"def do(n, *answers) -> Tuple[Optional[int], Optional[int]]:\n",
|
||||||
|
" \"E.g., when n is 3, return [day3_1(in3), day3_2(in3)], and verify answers.\"\n",
|
||||||
|
" got = [None, None]\n",
|
||||||
|
" g = globals()\n",
|
||||||
|
" for i in range(2):\n",
|
||||||
|
" fname = f'day{n}_{i + 1}'\n",
|
||||||
|
" if fname in g: \n",
|
||||||
|
" got[i] = g[fname](g[f'in{n}'])\n",
|
||||||
|
" if len(answers) > i: \n",
|
||||||
|
" assert got[i] == answers[i], f'{fname} expected {answers[i]}; got {got[i]}'\n",
|
||||||
|
" return got"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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 first(iterable, default=None) -> object:\n",
|
||||||
|
" \"Return first item in iterable, or default.\"\n",
|
||||||
|
" return next(iter(iterable), default)\n",
|
||||||
|
"\n",
|
||||||
|
"def multimap(items) -> 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",
|
||||||
|
"cat = ''.join"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Day 1: Report Repair\n",
|
||||||
|
"\n",
|
||||||
|
"1. Find the two entries in your expense report (a file of integers) that sum to 2020; what do you get if you multiply them together?\n",
|
||||||
|
"2. In your expense report, what is the product of the three entries that sum to 2020?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 4,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"in1: Set[int] = set(data(1, int))\n",
|
||||||
|
"\n",
|
||||||
|
"def day1_1(nums):\n",
|
||||||
|
" \"Find 2 distinct numbers that sum to 2020, and return their product.\"\n",
|
||||||
|
" return first(x * y for x in nums \n",
|
||||||
|
" for y in nums & {2020 - x} \n",
|
||||||
|
" if x != y)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 5,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def day1_2(nums):\n",
|
||||||
|
" \"Find 3 distinct numbers that sum to 2020, and return their product.\"\n",
|
||||||
|
" return first(x * y * z for x, y in combinations(nums, 2) \n",
|
||||||
|
" for z in nums & {2020 - x - y} \n",
|
||||||
|
" if x != z != y)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 6,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[787776, 262738554]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 6,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"do(1, 787776, 262738554)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Day 2: Password Philosophy\n",
|
||||||
|
"\n",
|
||||||
|
"1. A password policy is of the form `1-3 b: cdefg` meaning that the password must contain 1 to 3 instances of `b`; `cdefg` is invalid under this policy. How many passwords in your input file are valid according to their policies?\n",
|
||||||
|
"- JK! The policy actually means that exactly one of positions 1 and 3 must contain the letter `b`. How many passwords are valid according to the new interpretation of the policies?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 7,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def password_policy(policy) -> tuple:\n",
|
||||||
|
" \"Given policy='1-3 L: password', return (1, 3, 'L', 'password').\"\n",
|
||||||
|
" a, b, L, pw = re.findall(r'[^-:\\s]+', policy)\n",
|
||||||
|
" return (int(a), int(b), L, pw)\n",
|
||||||
|
"\n",
|
||||||
|
"in2: List[tuple] = data(2, password_policy)\n",
|
||||||
|
"\n",
|
||||||
|
"def valid_password(policy) -> bool: \n",
|
||||||
|
" \"Does policy's pw have between a and b instances of letter L?\"\n",
|
||||||
|
" a, b, L, pw = policy\n",
|
||||||
|
" return a <= pw.count(L) <= b\n",
|
||||||
|
"\n",
|
||||||
|
"def day2_1(policies): return quantify(policies, valid_password)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 8,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def day2_2(policies): return quantify(policies, valid_password_2)\n",
|
||||||
|
"\n",
|
||||||
|
"def valid_password_2(line) -> bool: \n",
|
||||||
|
" \"Does line's pw have letter L at position a or b (1-based), but not both?\"\n",
|
||||||
|
" a, b, L, pw = line\n",
|
||||||
|
" return (L == pw[a-1]) ^ (L == pw[b-1])"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 9,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[383, 272]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 9,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"do(2, 383, 272)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Day 3: Toboggan Trajectory\n",
|
||||||
|
"\n",
|
||||||
|
"The input file is a map of a field that looks like this:\n",
|
||||||
|
"\n",
|
||||||
|
" ..##.......\n",
|
||||||
|
" #...#...#..\n",
|
||||||
|
" .#....#..#.\n",
|
||||||
|
" ..#.#...#.#\n",
|
||||||
|
" .#...##..#.\n",
|
||||||
|
" \n",
|
||||||
|
"where each `#` is a tree and the pattern in each row implicitly repeats to the right.\n",
|
||||||
|
"\n",
|
||||||
|
"1. Starting at the top-left corner of your map and following a slope of down 1 and right 3, how many trees would you encounter?\n",
|
||||||
|
"2. What do you get if you multiply together the number of trees encountered on each of the slopes 1/1, 1/3, 1/5, 1/7, 2/1?\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 10,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"in3: List[str] = data(3)\n",
|
||||||
|
"\n",
|
||||||
|
"def day3_1(rows, dx=3, dy=1, tree='#'): \n",
|
||||||
|
" \"How many trees are on the coordinates on the slope dy/dx?\"\n",
|
||||||
|
" return quantify(row[dx * y % len(row)] == tree\n",
|
||||||
|
" for y, row in enumerate(rows[::dy]))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 11,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def day3_2(rows):\n",
|
||||||
|
" \"What is the product of the number of trees on these five slopes?\"\n",
|
||||||
|
" def t(dx, dy): return day3_1(rows, dx, dy)\n",
|
||||||
|
" return t(1, 1) * t(3, 1) * t(5, 1) * t(7, 1) * t(1, 2) "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 12,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[167, 736527114]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 12,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"do(3, 167, 736527114)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Day 4: Passport Processing\n",
|
||||||
|
"\n",
|
||||||
|
"The input is a file of passport data that looks like this (each passport is a series of field:value pairs separated from the next passport by a blank line):\n",
|
||||||
|
"\n",
|
||||||
|
" ecl:gry pid:860033327 eyr:2020 hcl:#fffffd\n",
|
||||||
|
" byr:1937 iyr:2017 cid:147 hgt:183cm\n",
|
||||||
|
"\n",
|
||||||
|
" iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884 hcl:#cfa07d byr:1929\n",
|
||||||
|
"\n",
|
||||||
|
" hcl:#ae17e1 iyr:2013\n",
|
||||||
|
" eyr:2024 ecl:brn pid:760753108 byr:1931 hgt:179cm\n",
|
||||||
|
" \n",
|
||||||
|
" \n",
|
||||||
|
"1. Count the number of valid passports — those that have all seven required fields (byr, ecl, eyr, hcl, hgt, iyr, pid). \n",
|
||||||
|
"2. Count the number of valid passports — those that have valid values for all required fields (see the rules in `valid_fields`). "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 13,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"Passport = dict\n",
|
||||||
|
"\n",
|
||||||
|
"def passport(text: str) -> Passport:\n",
|
||||||
|
" \"Make a dict of the 'key:val' entries in text.\"\n",
|
||||||
|
" return Passport(re.findall(r'([a-z]+):([^\\s]+)', text))\n",
|
||||||
|
"\n",
|
||||||
|
"assert passport('''a:1 b:2\n",
|
||||||
|
"see:3 d:four''') == {'a': '1', 'b': '2', 'see': '3', 'd': 'four'}\n",
|
||||||
|
"\n",
|
||||||
|
"in4: List[Passport] = data(4, passport, '\\n\\n') # Passports are separated by blank lines\n",
|
||||||
|
"\n",
|
||||||
|
"required_fields = {'byr', 'ecl', 'eyr', 'hcl', 'hgt', 'iyr', 'pid'}\n",
|
||||||
|
"\n",
|
||||||
|
"def valid_passport(passport) -> bool: return required_fields.issubset(passport)\n",
|
||||||
|
"\n",
|
||||||
|
"def day4_1(passports): return quantify(passports, valid_passport) "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 14,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def day4_2(passports): return quantify(passports, valid_fields)\n",
|
||||||
|
"\n",
|
||||||
|
"def valid_fields(passport) -> bool:\n",
|
||||||
|
" '''Validate fields according to the following rules:\n",
|
||||||
|
" byr (Birth Year) - four digits; at least 1920 and at most 2002.\n",
|
||||||
|
" iyr (Issue Year) - four digits; at least 2010 and at most 2020.\n",
|
||||||
|
" eyr (Expr. Year) - four digits; at least 2020 and at most 2030.\n",
|
||||||
|
" hgt (Height) - a number followed by either cm or in:\n",
|
||||||
|
" If cm, the number must be at least 150 and at most 193.\n",
|
||||||
|
" If in, the number must be at least 59 and at most 76.\n",
|
||||||
|
" hcl (Hair Color) - a '#' followed by exactly six characters 0-9 or a-f.\n",
|
||||||
|
" ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth.\n",
|
||||||
|
" pid (Passport ID) - a nine-digit number, including leading zeroes.\n",
|
||||||
|
" cid (Country ID) - ignored, missing or not.'''\n",
|
||||||
|
" return (valid_passport(passport)\n",
|
||||||
|
" and all(field_validator[field](passport[field])\n",
|
||||||
|
" for field in required_fields))\n",
|
||||||
|
"\n",
|
||||||
|
"field_validator = dict(\n",
|
||||||
|
" byr=lambda v: 1920 <= int(v) <= 2002,\n",
|
||||||
|
" iyr=lambda v: 2010 <= int(v) <= 2020,\n",
|
||||||
|
" eyr=lambda v: 2020 <= int(v) <= 2030,\n",
|
||||||
|
" hcl=lambda v: re.match('^#[0-9a-f]{6}$', v),\n",
|
||||||
|
" ecl=lambda v: v in ('amb', 'blu', 'brn', 'gry', 'grn', 'hzl', 'oth'),\n",
|
||||||
|
" pid=lambda v: len(v) == 9 and v.isnumeric(),\n",
|
||||||
|
" hgt=lambda v: ((v.endswith('cm') and 150 <= int(v[:-2]) <= 193) or\n",
|
||||||
|
" (v.endswith('in') and 59 <= int(v[:-2]) <= 76)))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 15,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[237, 172]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 15,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"do(4, 237, 172)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Day 5: Binary Boarding\n",
|
||||||
|
"\n",
|
||||||
|
"The input is a list of boarding passes, such as `BFFFBBFRRR`. Each boarding pass corrsponds to a **seat ID** using an encoding where B and F stand for the back and front half of the plane; R and L stand for right and left half of a row. The encoding is the same as substituting 0 for F or L and 1 for B or R and treating the result as a binary number.\n",
|
||||||
|
"\n",
|
||||||
|
"1. What is the highest seat ID on a boarding pass?\n",
|
||||||
|
"- What is the one missing seat ID, between the minimum and maximum IDs, that is not on the list of boarding passes?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 16,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def seat_id(seat: str, table=str.maketrans('FLBR', '0011')) -> int:\n",
|
||||||
|
" \"Treat a seat description as a binary number; convert to int.\"\n",
|
||||||
|
" return int(seat.translate(table), base=2)\n",
|
||||||
|
"\n",
|
||||||
|
"assert seat_id('FBFBBFFRLR') == 357\n",
|
||||||
|
"\n",
|
||||||
|
"in5: List[int] = data(5, seat_id)\n",
|
||||||
|
"\n",
|
||||||
|
"day5_1 = max # Find the maximum seat id."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 17,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def day5_2(ids):\n",
|
||||||
|
" \"Find the one missing seat id.\"\n",
|
||||||
|
" [missing] = set(range(min(ids), max(ids))) - set(ids)\n",
|
||||||
|
" return missing"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 18,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[906, 519]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 18,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"do(5, 906, 519)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Day 6: Custom Customs\n",
|
||||||
|
"\n",
|
||||||
|
"Each passenger fills out a customs form; passengers are arranged in groups. The \"yes\" answer are recorded; each person on one line, each group separated by a blank line. E.g.:\n",
|
||||||
|
"\n",
|
||||||
|
" abc\n",
|
||||||
|
"\n",
|
||||||
|
" a\n",
|
||||||
|
" b\n",
|
||||||
|
" c\n",
|
||||||
|
"\n",
|
||||||
|
" ab\n",
|
||||||
|
" ac\n",
|
||||||
|
" \n",
|
||||||
|
"1. For each group, count the number of questions to which *anyone* answered \"yes\". What is the sum of those counts?\n",
|
||||||
|
"2. For each group, count the number of questions to which *everyone* answered \"yes\". What is the sum of those counts?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 19,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"in6: List[List[str]] = data(6, str.splitlines, '\\n\\n')\n",
|
||||||
|
"\n",
|
||||||
|
"assert in6[1] == ['arke', 'qzr', 'plmgnr', 'uriq'] # A group is a list of strs\n",
|
||||||
|
"\n",
|
||||||
|
"def day6_1(groups): \n",
|
||||||
|
" \"For each group, compute the number of letters that ANYONE got. Sum them.\"\n",
|
||||||
|
" return sum(len(set(cat(group)))\n",
|
||||||
|
" for group in groups)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 20,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def day6_2(groups: List[List[str]]): \n",
|
||||||
|
" \"For each group, compute the number of letters that EVERYONE got. Sum them.\"\n",
|
||||||
|
" return sum(len(set.intersection(*map(set, group)))\n",
|
||||||
|
" for group in groups)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 21,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[6530, 3323]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 21,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"do(6, 6530, 3323)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Day 7: Handy Haversacks\n",
|
||||||
|
"\n",
|
||||||
|
"There are strict luggage processing rules for what color bags must contain what other bags. For example:\n",
|
||||||
|
"\n",
|
||||||
|
" light red bags contain 1 bright white bag, 2 muted yellow bags.\n",
|
||||||
|
" dark orange bags contain 3 bright white bags, 4 muted yellow bags.\n",
|
||||||
|
" bright white bags contain 1 shiny gold bag.\n",
|
||||||
|
" \n",
|
||||||
|
"1. How many bag colors must eventually contain at least one shiny gold bag?\n",
|
||||||
|
"2. How many individual bags must be inside your single shiny gold bag?\n",
|
||||||
|
"\n",
|
||||||
|
"I wasn't quite sure, but it appears that \"light red\" and \"dark red\" are different colors."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 22,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"Bag = str\n",
|
||||||
|
"\n",
|
||||||
|
"def parse_bag_rule(line: str) -> Tuple[Bag, Dict[Bag, int]]:\n",
|
||||||
|
" \"Return (outer_bag, {inner_bag: num, ...})\"\n",
|
||||||
|
" line = re.sub(' bags?|[.]', '', line) # Remove redundant info\n",
|
||||||
|
" outer, inner = line.split(' contain ')\n",
|
||||||
|
" return outer, dict(map(parse_bags, inner.split(', ')))\n",
|
||||||
|
"\n",
|
||||||
|
"def parse_bags(text) -> Tuple[Bag, int]:\n",
|
||||||
|
" \"Return the color and number of bags.\"\n",
|
||||||
|
" n, bag = text.split(maxsplit=1)\n",
|
||||||
|
" return bag, (0 if n == 'no' else int(n))\n",
|
||||||
|
"\n",
|
||||||
|
"in7: Dict[Bag, Dict[Bag, int]] = dict(data(7, parse_bag_rule))\n",
|
||||||
|
"\n",
|
||||||
|
"# in7 is of the following form:\n",
|
||||||
|
"assert (dict([parse_bag_rule(\"light gray bags contain 3 shiny gold bags, 1 dark red bag.\"),\n",
|
||||||
|
" parse_bag_rule(\"shiny gold bags contain 4 bright blue bags\")])\n",
|
||||||
|
" == {'light gray': {'shiny gold': 3, 'dark red': 1},\n",
|
||||||
|
" 'shiny gold': {'bright blue': 4}})\n",
|
||||||
|
"\n",
|
||||||
|
"assert parse_bags('3 muted gray') == ('muted gray', 3)\n",
|
||||||
|
"\n",
|
||||||
|
"def day7_1(rules, target='shiny gold'):\n",
|
||||||
|
" \"How many colors of bags can contain the target color bag?\"\"\"\n",
|
||||||
|
" return quantify(contains(bag, target, rules) for bag in rules)\n",
|
||||||
|
"\n",
|
||||||
|
"def contains(bag, target, rules) -> bool:\n",
|
||||||
|
" \"Does this bag contain the target (perhaps recursively)?\"\n",
|
||||||
|
" contents = rules.get(bag, {})\n",
|
||||||
|
" return (target in contents\n",
|
||||||
|
" or any(contains(inner, target, rules) for inner in contents))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 23,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def day7_2(rules, target='shiny gold'): return num_contained_in(target, rules)\n",
|
||||||
|
"\n",
|
||||||
|
"def num_contained_in(target, rules) -> int:\n",
|
||||||
|
" \"How many bags are contained in the target bag?\"\n",
|
||||||
|
" return sum(n + n * num_contained_in(bag, rules) \n",
|
||||||
|
" for (bag, n) in rules[target].items() if n > 0)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 24,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[103, 1469]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 24,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"do(7, 103, 1469)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Day 8: Handheld Halting\n",
|
||||||
|
"\n",
|
||||||
|
"The puzzle input is a program in an assembly language with three instructions: `jmp, acc, nop`. Since there is no conditional branch instruction, a program that executes any instruction twice will infinite loop; terminating programs will execute each instruction at most once.\n",
|
||||||
|
"\n",
|
||||||
|
"1. Immediately before any instruction is executed a second time, what value is in the accumulator register?\n",
|
||||||
|
"2. Fix the program so that it terminates normally by changing exactly one jmp to nop or nop to jmp. What is the value of the accumulator register after the program terminates?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 25,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"Instruction = Tuple[str, int] # e.g. ('jmp', +4)\n",
|
||||||
|
"Program = List[Instruction]\n",
|
||||||
|
"\n",
|
||||||
|
"def instruction(line: str) -> Program:\n",
|
||||||
|
" \"Parse a line of assembly code into an Instruction: an ('opcode', int) pair.\"\n",
|
||||||
|
" opcode, arg = line.split()\n",
|
||||||
|
" return opcode, int(arg)\n",
|
||||||
|
" \n",
|
||||||
|
"in8: Program = data(8, instruction)\n",
|
||||||
|
" \n",
|
||||||
|
"def day8_1(program):\n",
|
||||||
|
" \"Execte the program until it loops; then return accum.\"\n",
|
||||||
|
" pc = accum = 0\n",
|
||||||
|
" executed = set()\n",
|
||||||
|
" while True:\n",
|
||||||
|
" if pc in executed:\n",
|
||||||
|
" return accum\n",
|
||||||
|
" executed.add(pc)\n",
|
||||||
|
" opcode, arg = program[pc]\n",
|
||||||
|
" pc += 1\n",
|
||||||
|
" if opcode == 'acc':\n",
|
||||||
|
" accum += arg\n",
|
||||||
|
" if opcode == 'jmp':\n",
|
||||||
|
" pc = pc - 1 + arg"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"I had to think about what to do for Part 2. Do I need to make a flow graph of where the loops are? That sounds hard. But I soon realized that I can just use brute force—try every alteration of an instruction (there are only $O(n)$ of them), and run each altered program to see if it terminates (that too takes only $O(n)$ time)."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 26,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def day8_2(program): \n",
|
||||||
|
" \"Return the accumulator from the first altered program that terminates.\"\n",
|
||||||
|
" return first(accum for (terminates, accum) in map(run, altered_programs(program))\n",
|
||||||
|
" if terminates)\n",
|
||||||
|
"\n",
|
||||||
|
"def altered_programs(program, other=dict(jmp='nop', nop='jmp')) -> Iterator[Program]:\n",
|
||||||
|
" \"All ways to swap a nop for a jmp or vice-versa.\"\n",
|
||||||
|
" for i, (opcode, arg) in enumerate(program):\n",
|
||||||
|
" if opcode in other:\n",
|
||||||
|
" yield [*program[:i], (other[opcode], arg), *program[i + 1:]]\n",
|
||||||
|
"\n",
|
||||||
|
"def run(program) -> Tuple[bool, int]:\n",
|
||||||
|
" \"Run the program until it loops or terminates; return (terminates, accum)\"\n",
|
||||||
|
" pc = accum = 0\n",
|
||||||
|
" executed = set()\n",
|
||||||
|
" while 0 <= pc < len(program):\n",
|
||||||
|
" if pc in executed:\n",
|
||||||
|
" return False, accum # program loops\n",
|
||||||
|
" executed.add(pc)\n",
|
||||||
|
" opcode, arg = program[pc]\n",
|
||||||
|
" pc += 1\n",
|
||||||
|
" if opcode == 'acc':\n",
|
||||||
|
" accum += arg\n",
|
||||||
|
" if opcode == 'jmp':\n",
|
||||||
|
" pc = pc - 1 + arg\n",
|
||||||
|
" return True, accum # program terminates"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 27,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[1521, 1016]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 27,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"do(8, 1521, 1016)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Day 9: Encoding Error\n",
|
||||||
|
"\n",
|
||||||
|
"Given a list of numbers:\n",
|
||||||
|
"\n",
|
||||||
|
"1. Find the first number in the list (after the preamble of 25 numbers) which is not the sum of two of the 25 numbers before it.\n",
|
||||||
|
"2. Find a contiguous subsequence of numbers in your list which sum to the number from step 1; add the smallest and largest numbers in this subsequence.\n",
|
||||||
|
"\n",
|
||||||
|
"I could do this efficiently in $O(n)$ as in Day 1, but $n$ is so small I'll just use brute force."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 28,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"in9 = data(9, int)\n",
|
||||||
|
"\n",
|
||||||
|
"def day9_1(nums, p=25):\n",
|
||||||
|
" \"\"\"Find the first number in the list of numbers (after a preamble of p numbers) \n",
|
||||||
|
" which is not the sum of two of the p numbers before it.\"\"\"\n",
|
||||||
|
" return first(x for i, x in enumerate(nums) if i > p and x not in sum2(nums[i-p:i]))\n",
|
||||||
|
"\n",
|
||||||
|
"def sum2(nums): return map(sum, combinations(nums, 2))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 29,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def day9_2(nums, target=day9_1(in9)):\n",
|
||||||
|
" \"Find a contiguous subsequence of nums that sums to target; add their max and min.\"\n",
|
||||||
|
" subseq = find_subseq(nums, target)\n",
|
||||||
|
" return max(subseq) + min(subseq)\n",
|
||||||
|
"\n",
|
||||||
|
"def find_subseq(nums, target) -> deque:\n",
|
||||||
|
" \"Find a contiguous subsequence of nums that sums to target.\"\n",
|
||||||
|
" subseq = deque()\n",
|
||||||
|
" total = 0\n",
|
||||||
|
" for x in nums:\n",
|
||||||
|
" if total < target:\n",
|
||||||
|
" subseq.append(x)\n",
|
||||||
|
" total += x\n",
|
||||||
|
" if total == target:\n",
|
||||||
|
" return subseq\n",
|
||||||
|
" while total > target:\n",
|
||||||
|
" total -= subseq.popleft()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 30,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"[776203571, 104800569]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 30,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"do(9, 776203571, 104800569)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Summary"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 31,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"CPU times: user 226 ms, sys: 3.2 ms, total: 229 ms\n",
|
||||||
|
"Wall time: 231 ms\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"{1: [787776, 262738554],\n",
|
||||||
|
" 2: [383, 272],\n",
|
||||||
|
" 3: [167, 736527114],\n",
|
||||||
|
" 4: [237, 172],\n",
|
||||||
|
" 5: [906, 519],\n",
|
||||||
|
" 6: [6530, 3323],\n",
|
||||||
|
" 7: [103, 1469],\n",
|
||||||
|
" 8: [1521, 1016],\n",
|
||||||
|
" 9: [776203571, 104800569]}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 31,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"%time {i: do(i) for i in range(1, 10)}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 4
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user