Add files via upload

This commit is contained in:
Peter Norvig 2024-01-02 00:05:32 -08:00 committed by GitHub
parent 76c97b7f00
commit 280cf9a8e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 4578 additions and 175 deletions

4410
ipynb/Advent-2023.ipynb Normal file

File diff suppressed because it is too large Load Diff

View File

@ -4,16 +4,18 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"<div style=\"text-align: right\" align=\"right\"><i>Peter Norvig<br>Decembers 20162021</i></div>\n",
"<div style=\"text-align: right\" align=\"right\"><i>Peter Norvig<br>Decembers 20162023</i></div>\n",
"\n",
"# Advent of Code Utilities\n",
"\n",
"Stuff I might need for [Advent of Code](https://adventofcode.com). First, some imports that I have used in past AoC years:"
"Stuff I might need for [Advent of Code](https://adventofcode.com). \n",
"\n",
"First, some imports that I have used in past AoC years:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
@ -26,7 +28,6 @@
"from math import ceil, floor, factorial, gcd, log, log2, log10, sqrt, inf\n",
"\n",
"import matplotlib.pyplot as plt\n",
"\n",
"import ast\n",
"import fractions\n",
"import functools\n",
@ -35,6 +36,7 @@
"import pathlib\n",
"import re\n",
"import string\n",
"import sys\n",
"import time"
]
},
@ -65,19 +67,21 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"current_year = 2022 # Subdirectory name for input files\n",
"current_year = 2023 # Subdirectory name for input files\n",
"\n",
"lines = str.splitlines # By default, split input text into lines\n",
"\n",
"def paragraphs(text): \"Split text into paragraphs\"; return text.split('\\n\\n')\n",
"\n",
"def parse(day_or_text:Union[int, str], parser:Callable=str, sections:Callable=lines, show=8) -> tuple:\n",
"def parse(day_or_text:Union[int, str], parser=str, sections=lines, show=8) -> tuple:\n",
" \"\"\"Split the input text into `sections`, and apply `parser` to each.\n",
" The first argument is either the text itself, or the day number of a text file.\"\"\"\n",
" if isinstance(day_or_text, str) and show == 8: \n",
" show = 0 # By default, don't show lines when parsing exampole text.\n",
" show = 0 # By default, don't show lines when parsing example text.\n",
" start = time.time()\n",
" text = get_text(day_or_text)\n",
" show_items('Puzzle input', text.splitlines(), show)\n",
@ -86,7 +90,7 @@
" show_items('Parsed representation', records, show)\n",
" return records\n",
"\n",
"def get_text(day_or_text:Union[int, str]) -> str:\n",
"def get_text(day_or_text: Union[int, str]) -> str:\n",
" \"\"\"The text used as input to the puzzle: either a string or the day number,\n",
" which denotes the file 'AOC/year/input{day}.txt'.\"\"\"\n",
" if isinstance(day_or_text, str):\n",
@ -116,7 +120,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
@ -152,58 +156,6 @@
" return text.strip()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Helper functions:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def truncate(object, width=100, ellipsis=' ...') -> str:\n",
" \"\"\"Use elipsis to truncate `str(object)` to `width` characters, if necessary.\"\"\"\n",
" string = str(object)\n",
" return string if len(string) <= width else string[:width-len(ellipsis)] + ellipsis\n",
"\n",
"def mapt(function: Callable, *sequences) -> tuple:\n",
" \"\"\"`map`, with the result as a tuple.\"\"\"\n",
" return tuple(map(function, *sequences))\n",
"\n",
"def mapl(function: Callable, *sequences) -> list:\n",
" \"\"\"`map`, with the result as a list.\"\"\"\n",
" return list(map(function, *sequences))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Some tests for the functions above:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"assert parse(\"hello\\nworld\", show=0) == ('hello', 'world')\n",
"assert parse(\"123\\nabc7\", digits, show=0) == ((1, 2, 3), (7,))\n",
"assert truncate('hello world', 99) == 'hello world'\n",
"assert truncate('hello world', 8) == 'hell ...'\n",
"\n",
"assert atoms('hello, cruel_world! 24-7') == ('hello', 'cruel_world', 24, -7)\n",
"assert words('hello, cruel_world! 24-7') == ('hello', 'cruel', 'world')\n",
"assert digits('hello, cruel_world! 24-7') == (2, 4, 7)\n",
"assert ints('hello, cruel_world! 24-7') == (24, -7)\n",
"assert positive_ints('hello, cruel_world! 24-7') == (24, 7)"
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -215,32 +167,34 @@
},
{
"cell_type": "code",
"execution_count": 91,
"execution_count": 105,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" .0000 seconds, answer: 3 INCORRECT!!!! Expected 2"
"name": "stdout",
"output_type": "stream",
"text": [
" 0.1: .0000 seconds, answer unknown \n",
" 0.2: .0000 seconds, answer 549755813888 ok\n",
" 0.3: .0000 seconds, answer 549755813889 WRONG; expected answer is 549755813888\n",
"10.4: .0000 seconds, answer 4 WRONG; expected answer is unknown\n"
]
},
"execution_count": 91,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"answers = {} # `answers` is a dict of {puzzle_number: answer}\n",
"\n",
"unknown = 'unknown'\n",
"\n",
"class answer:\n",
" \"\"\"Verify that calling `code` computes the `solution` to `puzzle`. \n",
" Record results in the dict `answers`.\"\"\"\n",
" def __init__(self, puzzle, solution, code:callable):\n",
" self.solution, self.code = solution, code\n",
" def __init__(self, puzzle: float, solution, code:callable=lambda:unknown):\n",
" self.puzzle, self.solution, self.code = puzzle, solution, code\n",
" answers[puzzle] = self\n",
" self.check()\n",
" \n",
" def check(self):\n",
" def check(self) -> bool:\n",
" \"\"\"Check if the code computes the correct solution; record run time.\"\"\"\n",
" start = time.time()\n",
" self.got = self.code()\n",
@ -248,12 +202,19 @@
" self.ok = (self.got == self.solution)\n",
" return self.ok\n",
" \n",
" def __repr__(self):\n",
" def __repr__(self) -> str:\n",
" \"\"\"The repr of an answer shows what happened.\"\"\"\n",
" def commas(x) -> str: return f'{x:,d}' if is_int(x) else f'{x}'\n",
" secs = f'{self.secs:7.4f} seconds'.replace(' 0.', ' .')\n",
" ok = '' if self.ok else f' !!!! INCORRECT !!!! Expected {commas(self.solution)}'\n",
" return f'{secs}, answer: {commas(self.got)}{ok}'"
" secs = f'{self.secs:7.4f}'.replace(' 0.', ' .')\n",
" comment = (f'' if self.got == unknown else\n",
" f' ok' if self.ok else \n",
" f' WRONG; expected answer is {self.solution}')\n",
" return f'Puzzle {self.puzzle:4.1f}: {secs} seconds, answer {self.got:<15}{comment}'\n",
"\n",
"def test_answer():\n",
" print(answer(0.1, unknown))\n",
" print(answer(0.2, 2**39, lambda: 2**39))\n",
" print(answer(0.3, 2**39, lambda: 2**39+1))\n",
" print(answer(10.4, unknown, lambda: 2 + 2))"
]
},
{
@ -267,7 +228,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
@ -333,6 +294,10 @@
" \"Intersection of several sets; error if no sets.\"\n",
" first, *rest = sets\n",
" return set(first).intersection(*rest)\n",
"\n",
"def range_intersection(range1, range2) -> range:\n",
" \"\"\"Return a range that is the intersection of these two ranges.\"\"\"\n",
" return range(max(range1.start, range2.start), min(range1.stop, range2.stop))\n",
" \n",
"def naked_plot(points, marker='o', size=(10, 10), invert=True, square=False, **kwds):\n",
" \"\"\"Plot `points` without any axis lines or tick marks.\n",
@ -353,45 +318,32 @@
" return {dic[x]: x for x in dic}\n",
"\n",
"def walrus(name, value):\n",
" \"\"\"If you're not in 3.8, and you can't do `x := val`,\n",
" then you can use `walrus('x', val)`.\"\"\"\n",
" \"\"\"If you're not in 3.8 or more, and you can't do `x := val`,\n",
" then you can use `walrus('x', val)`, if `x` is global.\"\"\"\n",
" globals()[name] = value\n",
" return value\n",
"\n",
"cat = ''.join\n",
"def truncate(object, width=100, ellipsis=' ...') -> str:\n",
" \"\"\"Use elipsis to truncate `str(object)` to `width` characters, if necessary.\"\"\"\n",
" string = str(object)\n",
" return string if len(string) <= width else string[:width-len(ellipsis)] + ellipsis\n",
"\n",
"def mapt(function: Callable, *sequences) -> tuple:\n",
" \"\"\"`map`, with the result as a tuple.\"\"\"\n",
" return tuple(map(function, *sequences))\n",
"\n",
"def mapl(function: Callable, *sequences) -> list:\n",
" \"\"\"`map`, with the result as a list.\"\"\"\n",
" return list(map(function, *sequences))\n",
"\n",
"def cat(things: Collection) -> str:\n",
" \"\"\"Concatenate the things.\"\"\"\n",
" return ''.join(map(str, things))\n",
" \n",
"cache = functools.lru_cache(None)\n",
"Ø = frozenset() # empty set"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"More tests:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"assert multimap(((i % 3), i) for i in range(9)) == {0: [0, 3, 6], 1: [1, 4, 7], 2: [2, 5, 8]}\n",
"assert prod([2, 3, 5]) == 30\n",
"assert total(Counter('hello, world')) == 12\n",
"assert cover(3, 1, 4, 1, 5) == range(1, 6)\n",
"assert minmax([3, 1, 4, 1, 5, 9]) == (1, 9)\n",
"assert T([(1, 2, 3), (4, 5, 6)]) == [(1, 4), (2, 5), (3, 6)]\n",
"assert the({1}) == 1\n",
"assert split_at('hello, world', 6) == ('hello,', ' world')\n",
"assert is_int(-42) and not is_int('one')\n",
"assert sign(-42) == -1 and sign(0) == 0 and sign(42) == +1\n",
"assert union([{1, 2}, {3, 4}, {5, 6}]) == {1, 2, 3, 4, 5, 6}\n",
"assert intersection([{1, 2, 3}, {2, 3, 4}, {2, 4, 6, 8}]) == {2}\n",
"assert clock_mod(24, 12) == 12 and 24 % 12 == 0\n",
"assert cat(['hello', 'world']) == 'helloworld'"
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -403,7 +355,7 @@
},
{
"cell_type": "code",
"execution_count": 24,
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
@ -459,32 +411,6 @@
" return next((x for x in iterable if x), default)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"More tests:"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [],
"source": [
"assert quantify(words('This is a test'), str.islower) == 3\n",
"assert dotproduct([1, 2, 3, 4], [1000, 100, 10, 1]) == 1234\n",
"assert list(flatten([{1, 2, 3}, (4, 5, 6), [7, 8, 9]])) == [1, 2, 3, 4, 5, 6, 7, 8, 9]\n",
"assert append(([1, 2], [3, 4], [5, 6])) == [1, 2, 3, 4, 5, 6]\n",
"assert list(batched(range(11), 3)) == [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10)]\n",
"assert list(sliding_window('abcdefghi', 3)) == ['abc', 'bcd', 'cde', 'def', 'efg', 'fgh', 'ghi']\n",
"assert first('abc') == 'a'\n",
"assert first('') == None\n",
"assert last('abc') == 'c'\n",
"assert first_true([0, None, False, 42, 99]) == 42\n",
"assert first_true([0, None, '', 0.0]) == False"
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -496,7 +422,7 @@
},
{
"cell_type": "code",
"execution_count": 92,
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
@ -571,22 +497,28 @@
},
{
"cell_type": "code",
"execution_count": 68,
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"class Grid(dict):\n",
" \"\"\"A 2D grid, implemented as a mapping of {(x, y): cell_contents}.\"\"\"\n",
" def __init__(self, grid=(), directions=directions4, skip=(), default=KeyError):\n",
" \"\"\"Initialize with either (e.g.) `Grid({(0, 0): '#', (1, 0): '.', ...})`, or\n",
" `Grid([\"#..\", \"..#\"]) or `Grid(\"#..\\n..#\")`.\"\"\"\n",
" def __init__(self, grid=(), directions=directions4, skip=(), default=None):\n",
" \"\"\"Initialize one of four ways: \n",
" `Grid({(0, 0): '#', (1, 0): '.', ...})`\n",
" `Grid(another_grid)\n",
" `Grid([\"#..\", \"..#\"])\n",
" `Grid(\"#..\\n..#\")`.\"\"\"\n",
" self.directions = directions\n",
" self.skip = skip\n",
" self.default = default\n",
" if isinstance(grid, abc.Mapping): \n",
" self.update(grid) \n",
" self.size = (len(cover(Xs(self))), len(cover(Ys(self))))\n",
" else:\n",
" if isinstance(grid, str): \n",
" grid = grid.splitlines()\n",
" self.size = (max(map(len, grid)), len(grid))\n",
" self.update({(x, y): val \n",
" for y, row in enumerate(grid) \n",
" for x, val in enumerate(row)\n",
@ -599,23 +531,33 @@
" else:\n",
" return self.default\n",
"\n",
" def copy(self): return Grid(self, directions=self.directions, default=self.default)\n",
" def in_range(self, point) -> bool:\n",
" \"\"\"Is the point within the range of the grid's size?\"\"\"\n",
" return (0 <= X_(point) < X_(self.size) and\n",
" 0 <= Y_(point) < Y_(self.size))\n",
"\n",
" def copy(self): \n",
" return Grid(self, directions=self.directions, skip=self.skip, default=self.default)\n",
" \n",
" def neighbors(self, point) -> List[Point]:\n",
" \"\"\"Points on the grid that neighbor `point`.\"\"\"\n",
" return [add2(point, Δ) for Δ in self.directions \n",
" if add2(point, Δ) in self or self.default != KeyError]\n",
" if add2(point, Δ) in self or self.default not in (KeyError, None)]\n",
" \n",
" def neighbor_contents(self, point) -> Iterable:\n",
" \"\"\"The contents of the neighboring points.\"\"\"\n",
" return (self[p] for p in self.neighbors(point))\n",
"\n",
" def findall(self, contents: Collection) -> List[Point]:\n",
" \"\"\"All points that contain one of the given contents, e.g. grid.findall('#').\"\"\"\n",
" return [p for p in self if self[p] in contents]\n",
" \n",
" def to_rows(self, xrange=None, yrange=None) -> List[List[object]]:\n",
" \"\"\"The contents of the grid, as a rectangular list of lists.\n",
" You can define a window with an xrange and yrange; or they default to the whole grid.\"\"\"\n",
" xrange = xrange or cover(Xs(self))\n",
" yrange = yrange or cover(Ys(self))\n",
" default = ' ' if self.default is KeyError else self.default\n",
" default = ' ' if self.default in (KeyError, None) else self.default\n",
" return [[self.get((x, y), default) for x in xrange] \n",
" for y in yrange]\n",
"\n",
@ -637,28 +579,6 @@
" return [add(point, Δ) for Δ in directions]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here are some tests:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"p, q = (0, 3), (4, 0)\n",
"assert Y_(p) == 3 and X_(q) == 4\n",
"assert distance(p, q) == 5\n",
"assert taxi_distance(p, q) == 7\n",
"assert add(p, q) == (4, 3)\n",
"assert sub(p, q) == (-4, 3)\n",
"assert add(North, South) == (0, 0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -676,7 +596,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
@ -704,7 +624,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
@ -781,7 +701,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
@ -810,7 +730,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
@ -825,7 +745,7 @@
},
{
"cell_type": "code",
"execution_count": 42,
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
@ -839,7 +759,7 @@
},
{
"cell_type": "code",
"execution_count": 30,
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
@ -850,6 +770,79 @@
" def __setattr__(self, attr, value):\n",
" self[attr] = value"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Tests"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"def tests():\n",
" \"\"\"Run tests on utility functions. Also serves as usage examples.\"\"\"\n",
" \n",
" # PARSER\n",
"\n",
" assert parse(\"hello\\nworld\", show=0) == ('hello', 'world')\n",
" assert parse(\"123\\nabc7\", digits, show=0) == ((1, 2, 3), (7,))\n",
" assert truncate('hello world', 99) == 'hello world'\n",
" assert truncate('hello world', 8) == 'hell ...'\n",
"\n",
" assert atoms('hello, cruel_world! 24-7') == ('hello', 'cruel_world', 24, -7)\n",
" assert words('hello, cruel_world! 24-7') == ('hello', 'cruel', 'world')\n",
" assert digits('hello, cruel_world! 24-7') == (2, 4, 7)\n",
" assert ints('hello, cruel_world! 24-7') == (24, -7)\n",
" assert positive_ints('hello, cruel_world! 24-7') == (24, 7)\n",
"\n",
" # UTILITIES\n",
"\n",
" assert multimap(((i % 3), i) for i in range(9)) == {0: [0, 3, 6], 1: [1, 4, 7], 2: [2, 5, 8]}\n",
" assert prod([2, 3, 5]) == 30\n",
" assert total(Counter('hello, world')) == 12\n",
" assert cover(3, 1, 4, 1, 5) == range(1, 6)\n",
" assert minmax([3, 1, 4, 1, 5, 9]) == (1, 9)\n",
" assert T([(1, 2, 3), (4, 5, 6)]) == [(1, 4), (2, 5), (3, 6)]\n",
" assert the({1}) == 1\n",
" assert split_at('hello, world', 6) == ('hello,', ' world')\n",
" assert is_int(-42) and not is_int('one')\n",
" assert sign(-42) == -1 and sign(0) == 0 and sign(42) == +1\n",
" assert union([{1, 2}, {3, 4}, {5, 6}]) == {1, 2, 3, 4, 5, 6}\n",
" assert intersection([{1, 2, 3}, {2, 3, 4}, {2, 4, 6, 8}]) == {2}\n",
" assert clock_mod(24, 12) == 12 and 24 % 12 == 0\n",
" assert cat(['hello', 'world']) == 'helloworld'\n",
"\n",
" # ITERTOOL RECIPES\n",
"\n",
" assert quantify(words('This is a test'), str.islower) == 3\n",
" assert dotproduct([1, 2, 3, 4], [1000, 100, 10, 1]) == 1234\n",
" assert list(flatten([{1, 2, 3}, (4, 5, 6), [7, 8, 9]])) == [1, 2, 3, 4, 5, 6, 7, 8, 9]\n",
" assert append(([1, 2], [3, 4], [5, 6])) == [1, 2, 3, 4, 5, 6]\n",
" assert list(batched(range(11), 3)) == [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10)]\n",
" assert list(sliding_window('abcdefghi', 3)) == ['abc', 'bcd', 'cde', 'def', 'efg', 'fgh', 'ghi']\n",
" assert first('abc') == 'a'\n",
" assert first('') == None\n",
" assert last('abc') == 'c'\n",
" assert first_true([0, None, False, 42, 99]) == 42\n",
" assert first_true([0, None, '', 0.0]) == False\n",
"\n",
" # POINTS\n",
"\n",
" p, q = (0, 3), (4, 0)\n",
" assert Y_(p) == 3 and X_(q) == 4\n",
" assert distance(p, q) == 5\n",
" assert taxi_distance(p, q) == 7\n",
" assert add(p, q) == (4, 3)\n",
" assert sub(p, q) == (-4, 3)\n",
" assert add(North, South) == (0, 0)\n",
" \n",
"tests()"
]
}
],
"metadata": {