Add files via upload

This commit is contained in:
Peter Norvig
2021-12-06 22:51:28 -08:00
committed by GitHub
parent f374494d41
commit 601947e5d4

View File

@@ -8,20 +8,43 @@
"\n", "\n",
"# Advent of Code 2021\n", "# Advent of Code 2021\n",
"\n", "\n",
"I'm going to solve some [Advent of Code 2021](https://adventofcode.com/) (AoC) puzzles, but I won't be competing for time. \n", "I'm going to solve some [Advent of Code 2021](https://adventofcode.com/) (AoC) puzzles, but I won't be competing for points. \n",
"\n", "\n",
"I also won't explain each puzzle here; you'll have to click on each day's link (e.g. [Day 1](https://adventofcode.com/2021/day/1)) to understand the puzzle. \n", "I won't explain each puzzle here; you'll have to click on each day's link (e.g. [Day 1](https://adventofcode.com/2021/day/1)) to understand the puzzle. \n",
"\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). Except for errors, I will show the code as I developed it; I won't go back and refactor the code for part 1 when I see part 2." "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). Except for errors, I will show the code as I developed it; I won't go back and refactor the code for part 1 when I see part 2.\n",
"\n",
"# Day 0: Imports and Utilities\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 Dict, Tuple, Set, List, Iterator, Optional, Union\n",
"from statistics import mean\n",
"\n",
"import functools\n",
"import math\n",
"import re\n",
"\n",
"cat = ''.join\n",
"flatten = chain.from_iterable\n",
"cache = functools.lru_cache(None)"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"# Day 0: Imports and Utilities\n", "Now two functions that I will use each day, `parse` and `answer`. I will start by writing something like this for part 1 of Day 1:\n",
"\n",
"Below are the imports and utility functions that I found to be generally useful for these kinds of problems. But first two functions that I will use each day, `parse` and `answer`. I will start by writing something like this for part 1 of Day 1:\n",
"\n", "\n",
" in1 = parse(1, int)\n", " in1 = parse(1, int)\n",
" def solve_it(numbers): return ...\n", " def solve_it(numbers): return ...\n",
@@ -32,16 +55,16 @@
" in1 = parse(1, int)\n", " in1 = parse(1, int)\n",
" \n", " \n",
" def solve_it(numbers): return ...\n", " def solve_it(numbers): return ...\n",
" answer(1.1, solve_it(in1), ...)\n", " answer(1.1, solve_it(in1), 123)\n",
" \n", " \n",
" def solve_it2(numbers): return ...\n", " def solve_it2(numbers): return ...\n",
" answer(1.2, solve_it2(in1), ...)\n", " answer(1.2, solve_it2(in1), 123456)\n",
" \n" " \n"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 2,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@@ -57,27 +80,10 @@
] ]
}, },
{ {
"cell_type": "code", "cell_type": "markdown",
"execution_count": 2,
"metadata": {}, "metadata": {},
"outputs": [],
"source": [ "source": [
"from __future__ import annotations\n", "Two useful parsers are `ints` and `atoms`:"
"from collections import Counter, defaultdict, namedtuple, deque\n",
"from itertools import permutations, combinations, chain, count as count_from, product as cross_product\n",
"from functools import lru_cache\n",
"from typing import Dict, Tuple, Set, List, Iterator, Optional, Union\n",
"\n",
"import operator\n",
"import math\n",
"import ast\n",
"import sys\n",
"import re\n",
"\n",
"Char = str # Type used to indicate a single character\n",
"cat = ''.join\n",
"flatten = chain.from_iterable\n",
"cache = lru_cache(None)"
] ]
}, },
{ {
@@ -85,6 +91,38 @@
"execution_count": 3, "execution_count": 3,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [
"def ints(text: str) -> Tuple[int]:\n",
" \"Return a tuple of all the integers in text, ignoring non-numbers.\"\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",
" \"Parse text into atoms (numbers or strs).\"\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",
" val = float(text)\n",
" return round(val) if round(val) == val else val\n",
" except ValueError:\n",
" return text"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Some additional useful utility functions:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [ "source": [
"def quantify(iterable, pred=bool) -> int:\n", "def quantify(iterable, pred=bool) -> int:\n",
" \"Count the number of items in iterable for which pred is true.\"\n", " \"Count the number of items in iterable for which pred is true.\"\n",
@@ -110,27 +148,11 @@
" result *= n\n", " result *= n\n",
" return result\n", " return result\n",
"\n", "\n",
"def ints(text: str) -> Tuple[int]:\n",
" \"Return a tuple of all the integers in text, ignoring non-numbers.\"\n",
" return tuple(map(int, re.findall('-?[0-9]+', text)))\n",
"\n",
"def sign(x) -> int:\n", "def sign(x) -> int:\n",
" \"\"\"The sign of a number: +1, 0, or -1.\"\"\"\n", " \"\"\"The sign of a number: +1, 0, or -1.\"\"\"\n",
" return (0 if x == 0 else +1 if x > 0 else -1)\n", " return (0 if x == 0 else +1 if x > 0 else -1)\n",
"\n", "\n",
"Atom = Union[float, int, str]\n", "def median(numbers) -> float: return sorted(numbers)[len(numbers) // 2]\n",
"\n",
"def atoms(text: str, sep=None) -> Tuple[Atom]:\n",
" \"Parse text into atoms (numbers or strs).\"\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",
" val = float(text)\n",
" return round(val) if round(val) == val else val\n",
" except ValueError:\n",
" return text\n",
" \n", " \n",
"def dotproduct(A, B) -> float: return sum(a * b for a, b in zip(A, B))\n", "def dotproduct(A, B) -> float: return sum(a * b for a, b in zip(A, B))\n",
"\n", "\n",
@@ -153,7 +175,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 5,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@@ -162,7 +184,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 6,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@@ -171,7 +193,7 @@
"True" "True"
] ]
}, },
"execution_count": 5, "execution_count": 6,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -187,7 +209,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 7,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@@ -196,7 +218,7 @@
"True" "True"
] ]
}, },
"execution_count": 6, "execution_count": 7,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -219,12 +241,12 @@
"The input is a list of instructions, such as \"`forward 10`\".\n", "The input is a list of instructions, such as \"`forward 10`\".\n",
"\n", "\n",
"1. Follow instructions and report the product of your final horizontal position and depth.\n", "1. Follow instructions and report the product of your final horizontal position and depth.\n",
"1. Follow *revised* instructions and report the product of your final horizontal position and depth. (There is an \"aim\" which is increased by down and up instructions. Depth is changed not by down and up, but by going forward *n* units, which changes depth by aim × *n* units" "1. Follow *revised* instructions and report the product of your final horizontal position and depth. (There is an \"aim\" which is increased by down and up instructions. Depth is changed not by down and up, but by going forward *n* units, which changes depth by aim × *n* units.)"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 8,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@@ -233,7 +255,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 9,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@@ -242,7 +264,7 @@
"True" "True"
] ]
}, },
"execution_count": 8, "execution_count": 9,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -262,7 +284,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 9, "execution_count": 10,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@@ -271,7 +293,7 @@
"True" "True"
] ]
}, },
"execution_count": 9, "execution_count": 10,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -304,7 +326,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 11,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@@ -313,7 +335,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 31, "execution_count": 12,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@@ -322,7 +344,7 @@
"True" "True"
] ]
}, },
"execution_count": 31, "execution_count": 12,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -351,7 +373,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 30, "execution_count": 13,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@@ -360,7 +382,7 @@
"True" "True"
] ]
}, },
"execution_count": 30, "execution_count": 13,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -399,7 +421,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 13, "execution_count": 14,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@@ -408,7 +430,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 14, "execution_count": 15,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@@ -417,7 +439,7 @@
"True" "True"
] ]
}, },
"execution_count": 14, "execution_count": 15,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -437,7 +459,7 @@
" \"\"\"Are any boards winners due to the number just called (and previously drawn numbers)?\"\"\"\n", " \"\"\"Are any boards winners due to the number just called (and previously drawn numbers)?\"\"\"\n",
" return [board for board in boards\n", " return [board for board in boards\n",
" if just_called in board\n", " if just_called in board\n",
" and any(quantify(board[n] in drawn for n in line) == B \n", " and any(all(board[n] in drawn for n in line)\n",
" for line in lines(board.index(just_called)))]\n", " for line in lines(board.index(just_called)))]\n",
"\n", "\n",
"def bingo_score(board, drawn, just_called) -> int:\n", "def bingo_score(board, drawn, just_called) -> int:\n",
@@ -458,7 +480,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 15, "execution_count": 16,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@@ -467,7 +489,7 @@
"True" "True"
] ]
}, },
"execution_count": 15, "execution_count": 16,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -501,7 +523,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 16, "execution_count": 17,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@@ -510,7 +532,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 17, "execution_count": 18,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@@ -519,7 +541,7 @@
"True" "True"
] ]
}, },
"execution_count": 17, "execution_count": 18,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -549,7 +571,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 18, "execution_count": 19,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@@ -558,7 +580,7 @@
"True" "True"
] ]
}, },
"execution_count": 18, "execution_count": 19,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -600,7 +622,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 19, "execution_count": 20,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@@ -616,7 +638,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 25, "execution_count": 21,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@@ -625,7 +647,7 @@
"True" "True"
] ]
}, },
"execution_count": 25, "execution_count": 21,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -654,12 +676,12 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"My hunch was right, so part 2 is easy:" "My hunch was right, so part 2 is simple:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 26, "execution_count": 22,
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
@@ -668,7 +690,7 @@
"True" "True"
] ]
}, },
"execution_count": 26, "execution_count": 22,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@@ -681,15 +703,191 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"# [Day 7](https://adventofcode.com/2021/day/7):" "# [Day 7](https://adventofcode.com/2021/day/7): The Treachery of Whales\n",
"\n",
"The input is a single line of integers given the horizontal positions of each member of a swarm of crabs.\n",
"\n",
"1. Determine the horizontal position that the crabs can align to using the least fuel possible. (Each unit of travel costs one unit of fuel.) How much fuel must they spend to align to that position?\n",
"2. Determine the horizontal position that the crabs can align to using the least fuel possible. (Now for each crab the first unit of travel costs 1, the second 2, and so on.) How much fuel must they spend to align to that position?"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 23,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [] "source": [
"in7 = parse(7, ints)[0]"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 24,
"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": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 25,
"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, with nonlinear fuel costs?\"\"\"\n",
" # I don't know the best alignment point, so I'll try all of them\n",
" return min(sum(travel_cost(p, align) for p in positions)\n",
" for align in range(min(positions), max(positions)))\n",
"\n",
"def travel_cost(p, align) -> int:\n",
" \"\"\"The first step costs 1, the second 2, etc: 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": [
"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. However, what we really want to minimize is (steps<sup>2</sup> + steps), so that does not quite apply. Still, let's see what happens when we align to the mean:"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"95519083.0"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"positions = in7\n",
"align = mean(positions)\n",
"sum(travel_cost(p, align) for p in positions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Wait, that's even *better* than the correct answer; what's up with that? It must be that the mean is not an integer:"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"490.543"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mean(positions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We get the correct answer if we round the mean down (but not if we round up):"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"95519693"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"align = 490\n",
"sum(travel_cost(p, align) for p in positions)"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"95519725"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"align = 491\n",
"sum(travel_cost(p, align) for p in positions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# [Day 8](https://adventofcode.com/2021/day/8): ???"
]
} }
], ],
"metadata": { "metadata": {