This commit is contained in:
Peter Norvig 2022-12-14 00:15:57 -08:00 committed by GitHub
parent e31786e7e4
commit 778f22dbde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 477 additions and 195 deletions

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,7 @@
},
{
"cell_type": "code",
"execution_count": 20,
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
@ -24,16 +24,17 @@
"from typing import *\n",
"from statistics import mean, median\n",
"from math import ceil, floor, factorial, gcd, log, log2, log10, sqrt, inf\n",
"from functools import reduce\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import time\n",
"import heapq\n",
"import string\n",
"\n",
"import ast\n",
"import functools\n",
"import heapq\n",
"import operator\n",
"import pathlib\n",
"import re"
"import re\n",
"import string\n",
"import time"
]
},
{
@ -55,12 +56,12 @@
"- **Part 2**: Repeat the above steps for Part 2.\n",
"- Occasionally I'll introduce a **Part 3** where I explore beyond the official instructions.\n",
"\n",
"Here is `parse` and some helper functions for it:"
"Here is `parse`:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
@ -108,14 +109,19 @@
" return lambda section: next(fns)(section)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Functions that can be used as the `parser` argument to `parse` (also, consider `str.split` to split the line on whitespace): "
]
},
{
"cell_type": "code",
"execution_count": 21,
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"## Functions that can be used by `parse`\n",
"\n",
"Char = str # Intended as the type of a one-character string\n",
"Atom = Union[str, float, int] # The type of a string or number\n",
"\n",
@ -147,9 +153,16 @@
" except ValueError:\n",
" return text.strip()\n",
" \n",
"def mapt(function: Callable, sequence) -> tuple:\n",
"def mapt(function: Callable, *sequences) -> tuple:\n",
" \"\"\"`map`, with the result as a tuple.\"\"\"\n",
" return tuple(map(function, sequence))"
" return tuple(map(function, *sequences))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Some tests for the functions above:"
]
},
{
@ -158,8 +171,6 @@
"metadata": {},
"outputs": [],
"source": [
"## TESTS\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",
@ -196,9 +207,9 @@
" def pretty(x): return f'{x:,d}' if is_int(x) else truncate(x)\n",
" start = time.time()\n",
" got = code()\n",
" dt = time.time() - start\n",
" secs = time.time() - start\n",
" ans = pretty(got)\n",
" msg = f'{dt:5.3f} seconds for ' + (\n",
" msg = f'{secs:5.3f} seconds for ' + (\n",
" f'correct answer: {ans}' if (got == correct) else\n",
" f'WRONG!! ANSWER: {ans}; EXPECTED {pretty(correct)}')\n",
" answers[puzzle] = msg\n",
@ -216,14 +227,10 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 17,
"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",
"class multimap(defaultdict):\n",
" \"\"\"A mapping of {key: [val1, val2, ...]}.\"\"\"\n",
" def __init__(self, pairs: Iterable[tuple], symmetric=False):\n",
@ -242,6 +249,10 @@
" result *= x\n",
" return result\n",
"\n",
"def T(matrix: Sequence[Sequence]) -> List[Tuple]:\n",
" \"\"\"The transpose of a matrix: T([(1,2,3), (4,5,6)]) == [(1,4), (2,5), (3,6)]\"\"\"\n",
" return list(zip(*matrix))\n",
"\n",
"def total(counter: Counter) -> int: \n",
" \"\"\"The sum of all the counts in a Counter.\"\"\"\n",
" return sum(counter.values())\n",
@ -251,14 +262,6 @@
" numbers = list(numbers)\n",
" return min(numbers), max(numbers)\n",
"\n",
"def first(iterable) -> Optional[object]: \n",
" \"\"\"The first element in an iterable, or None.\"\"\"\n",
" return next(iter(iterable), None)\n",
"\n",
"def T(matrix: Sequence[Sequence]) -> List[Tuple]:\n",
" \"\"\"The transpose of a matrix: T([(1,2,3), (4,5,6)]) == [(1,4), (2,5), (3,6)]\"\"\"\n",
" return list(zip(*matrix))\n",
"\n",
"def cover(*integers) -> range:\n",
" \"\"\"A `range` that covers all the given integers, and any in between them.\n",
" cover(lo, hi) is a an inclusive (or closed) range, equal to range(lo, hi + 1).\"\"\"\n",
@ -275,6 +278,93 @@
" \"\"\"The sequence split into two pieces: (before position i, and i-and-after).\"\"\"\n",
" return sequence[:i], sequence[i:]\n",
"\n",
"def ignore(*args) -> None: \"Just return None.\"; return None\n",
"\n",
"def is_int(x) -> bool: \"Is x an int?\"; return isinstance(x, int) \n",
"\n",
"def sign(x) -> int: \"0, +1, or -1\"; return (0 if x == 0 else +1 if x > 0 else -1)\n",
"\n",
"def union(sets) -> set: \"Union of several sets\"; return set().union(*sets)\n",
"\n",
"def intersection(sets):\n",
" \"Intersection of several sets; error if no sets.\"\n",
" first, *rest = sets\n",
" return set(first).intersection(*rest)\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",
" Optionally specify size, whether square or not, and whether to invery y axis.\"\"\"\n",
" if size: plt.figure(figsize=((size, size) if is_int(size) else size))\n",
" plt.plot(*T(points), marker, **kwds)\n",
" if square: plt.axis('square')\n",
" plt.axis('off')\n",
" if invert: plt.gca().invert_yaxis()\n",
" \n",
"def clock_mod(i, m) -> int:\n",
" \"\"\"i % m, but replace a result of 0 with m\"\"\"\n",
" # This is like a clock, where 24 mod 12 is 12, not 0.\n",
" return (i % m) or m\n",
"\n",
"cat = ''.join\n",
"cache = functools.lru_cache(None)"
]
},
{
"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": {},
"source": [
"# Itertools Recipes\n",
"\n",
"The Python docs for the `itertools` module has some [\"recipes\"](https://docs.python.org/3/library/itertools.html#itertools-recipes) that I include here (some I have slightly modified):"
]
},
{
"cell_type": "code",
"execution_count": 8,
"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 dotproduct(vec1, vec2):\n",
" \"\"\"The dot product of two vectors.\"\"\"\n",
" return sum(map(operator.mul, vec1, vec2))\n",
"\n",
"flatten = chain.from_iterable # Yield items from each sequence in turn\n",
"\n",
"def append(sequences) -> Sequence: \"Append into a list\"; return list(flatten(sequences))\n",
"\n",
"def batched(data, n) -> list:\n",
" \"Batch data into lists of length n. The last batch may be shorter.\"\n",
" # batched('ABCDEFG', 3) --> ABC DEF G\n",
@ -284,68 +374,37 @@
" \"\"\"All length-n subsequences of sequence.\"\"\"\n",
" return (sequence[i:i+n] for i in range(len(sequence) + 1 - n))\n",
"\n",
"def ignore(*args) -> None: \"Just return None.\"; return None\n",
"def first(iterable, default=None) -> Optional[object]: \n",
" \"\"\"The first element in an iterable, or the default if iterable is empty.\"\"\"\n",
" return next(iter(iterable), default)\n",
"\n",
"def is_int(x) -> bool: \"Is x an int?\"; return isinstance(x, int) \n",
"\n",
"def sign(x) -> int: \"0, +1, or -1\"; return (0 if x == 0 else +1 if x > 0 else -1)\n",
"\n",
"def append(sequences) -> Sequence: \"Append sequences into a list\"; return list(flatten(sequences))\n",
"\n",
"def union(sets) -> set: \"Union of several sets\"; return set().union(*sets)\n",
"\n",
"def intersection(sets):\n",
" \"Intersection of several sets.\"\n",
" first, *rest = sets\n",
" return set(first).intersection(*rest)\n",
"\n",
"def square_plot(points, marker='o', size=12, extra=None, **kwds):\n",
" \"\"\"Plot `points` in a square of given `size`, with no axis labels.\n",
" Calls `extra()` to do more plt.* stuff if defined.\"\"\"\n",
" plt.figure(figsize=(size, size))\n",
" plt.plot(*T(points), marker, **kwds)\n",
" if extra: extra()\n",
" plt.axis('square'); plt.axis('off'); plt.gca().invert_yaxis()\n",
" \n",
"def clock_mod(i, m) -> int:\n",
" \"\"\"i % m, but replace a result of 0 with m\"\"\"\n",
" # This is like a clock, where 24 mod 12 is 12, not 0.\n",
" return (i % m) or m\n",
"\n",
"flatten = chain.from_iterable # Yield items from each sequence in turn\n",
"cat = ''.join\n",
"cache = functools.lru_cache(None)"
"def first_true(iterable, default=False):\n",
" \"\"\"Returns the first true value in the iterable.\n",
" If no true value is found, returns `default`.\"\"\"\n",
" return next((x for x in iterable if x), default)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"More tests:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"## TESTS\n",
"\n",
"assert quantify(words('This is a test'), str.islower) == 3\n",
"assert mapt(first, words('This is a test')) == ('T', 'i', 'a', 't')\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 first('abc') == 'a'\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 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 batched('abcdefghi', 3) == ['abc', 'def', 'ghi']\n",
"assert list(sliding_window('abcdefghi', 3)) == ['abc', 'bcd', 'cde', 'def', 'efg', 'fgh', 'ghi']\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 append(([1, 2], [3, 4], [5, 6])) == [1, 2, 3, 4, 5, 6]\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 list(flatten(['abc', 'def', '123'])) == ['a', 'b', 'c', 'd', 'e', 'f', '1', '2', '3']\n",
"assert cat(['hello', 'world']) == 'helloworld'"
"assert first('abc') == 'a'\n",
"assert first_true([0, None, False, 42, 99]) == 42"
]
},
{
@ -359,7 +418,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
@ -391,39 +450,21 @@
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"## TESTS\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 manhatten_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": "code",
"execution_count": 8,
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"class Grid(dict):\n",
" \"\"\"A 2D grid, implemented as a mapping of {(x, y): cell_contents}.\"\"\"\n",
" def __init__(self, mapping_or_rows, directions=directions4):\n",
" def __init__(self, mapping_or_rows=(), directions=directions4):\n",
" \"\"\"Initialize with either (e.g.) `Grid({(0, 0): 1, (1, 0): 2, ...})`, or\n",
" `Grid([(1, 2, 3), (4, 5, 6)]).\"\"\"\n",
" self.directions = directions\n",
" self.update(mapping_or_rows if isinstance(mapping_or_rows, abc.Mapping) else\n",
" {(x, y): val \n",
" for y, row in enumerate(mapping_or_rows) \n",
" for x, val in enumerate(row)})\n",
" self.width = max(map(X_, self)) + 1\n",
" self.height = max(map(Y_, self)) + 1\n",
" self.directions = directions\n",
"\n",
" \n",
" def copy(self): return Grid(self, directions=self.directions)\n",
" \n",
@ -431,14 +472,43 @@
" \"\"\"Points on the grid that neighbor `point`.\"\"\"\n",
" return [add(point, Δ) for Δ in self.directions if add(point, Δ) in self]\n",
" \n",
" def to_rows(self, default='.') -> List[List[object]]:\n",
" def to_rows(self, default='.', Xs=None, Ys=None) -> List[List[object]]:\n",
" \"\"\"The contents of the grid in a rectangular list of lists.\"\"\"\n",
" return [[self.get((x, y), default) for x in range(self.width)]\n",
" for y in range(self.height)]\n",
" Xs = Xs or range(max(map(X_, self)) + 1)\n",
" Ys = Ys or range(max(map(Y_, self)) + 1)\n",
" return [[self.get((x, y), default) for x in Xs] for y in Ys]\n",
" \n",
" def to_picture(self, sep='', default='.') -> str:\n",
" \"\"\"The contents of the grid as a picture.\"\"\"\n",
" return '\\n'.join(map(cat, self.to_rows(default)))"
" def to_picture(self, sep='', default='.', Xs=None, Ys=None) -> str:\n",
" \"\"\"The contents of the grid as a picture. Youi can specify the `Xs` and `Ys` to include.\"\"\"\n",
" return '\\n'.join(map(sep.join, self.to_rows(default, Xs, Ys)))\n",
" \n",
" def plot(self, markers, figsize=(14, 14), **kwds):\n",
" plt.figure(figsize=figsize)\n",
" plt.gca().invert_yaxis()\n",
" for m in markers:\n",
" plt.plot(*T(p for p in self if self[p] == m), markers.get(m, m), **kwds)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Tests:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"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 manhatten_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)"
]
},
{
@ -452,7 +522,7 @@
},
{
"cell_type": "code",
"execution_count": 18,
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
@ -480,7 +550,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
@ -545,7 +615,7 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [