This commit is contained in:
Peter Norvig
2022-12-14 00:15:57 -08:00
committed by GitHub
parent e31786e7e4
commit 778f22dbde
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", "cell_type": "code",
"execution_count": 20, "execution_count": 1,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@@ -24,16 +24,17 @@
"from typing import *\n", "from typing import *\n",
"from statistics import mean, median\n", "from statistics import mean, median\n",
"from math import ceil, floor, factorial, gcd, log, log2, log10, sqrt, inf\n", "from math import ceil, floor, factorial, gcd, log, log2, log10, sqrt, inf\n",
"from functools import reduce\n",
"\n", "\n",
"import matplotlib.pyplot as plt\n", "import matplotlib.pyplot as plt\n",
"import time\n", "\n",
"import heapq\n", "import ast\n",
"import string\n",
"import functools\n", "import functools\n",
"import heapq\n",
"import operator\n", "import operator\n",
"import pathlib\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", "- **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", "- Occasionally I'll introduce a **Part 3** where I explore beyond the official instructions.\n",
"\n", "\n",
"Here is `parse` and some helper functions for it:" "Here is `parse`:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 19, "execution_count": 2,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@@ -108,14 +109,19 @@
" return lambda section: next(fns)(section)" " 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", "cell_type": "code",
"execution_count": 21, "execution_count": 3,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"## Functions that can be used by `parse`\n",
"\n",
"Char = str # Intended as the type of a one-character string\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", "Atom = Union[str, float, int] # The type of a string or number\n",
"\n", "\n",
@@ -147,9 +153,16 @@
" except ValueError:\n", " except ValueError:\n",
" return text.strip()\n", " return text.strip()\n",
" \n", " \n",
"def mapt(function: Callable, sequence) -> tuple:\n", "def mapt(function: Callable, *sequences) -> tuple:\n",
" \"\"\"`map`, with the result as a 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": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"## TESTS\n",
"\n",
"assert parse(\"hello\\nworld\", show=0) == ('hello', 'world')\n", "assert parse(\"hello\\nworld\", show=0) == ('hello', 'world')\n",
"assert parse(\"123\\nabc7\", digits, show=0) == ((1, 2, 3), (7,))\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', 99) == 'hello world'\n",
@@ -196,9 +207,9 @@
" def pretty(x): return f'{x:,d}' if is_int(x) else truncate(x)\n", " def pretty(x): return f'{x:,d}' if is_int(x) else truncate(x)\n",
" start = time.time()\n", " start = time.time()\n",
" got = code()\n", " got = code()\n",
" dt = time.time() - start\n", " secs = time.time() - start\n",
" ans = pretty(got)\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'correct answer: {ans}' if (got == correct) else\n",
" f'WRONG!! ANSWER: {ans}; EXPECTED {pretty(correct)}')\n", " f'WRONG!! ANSWER: {ans}; EXPECTED {pretty(correct)}')\n",
" answers[puzzle] = msg\n", " answers[puzzle] = msg\n",
@@ -216,14 +227,10 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 6, "execution_count": 17,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "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", "class multimap(defaultdict):\n",
" \"\"\"A mapping of {key: [val1, val2, ...]}.\"\"\"\n", " \"\"\"A mapping of {key: [val1, val2, ...]}.\"\"\"\n",
" def __init__(self, pairs: Iterable[tuple], symmetric=False):\n", " def __init__(self, pairs: Iterable[tuple], symmetric=False):\n",
@@ -242,6 +249,10 @@
" result *= x\n", " result *= x\n",
" return result\n", " return result\n",
"\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", "def total(counter: Counter) -> int: \n",
" \"\"\"The sum of all the counts in a Counter.\"\"\"\n", " \"\"\"The sum of all the counts in a Counter.\"\"\"\n",
" return sum(counter.values())\n", " return sum(counter.values())\n",
@@ -251,14 +262,6 @@
" numbers = list(numbers)\n", " numbers = list(numbers)\n",
" return min(numbers), max(numbers)\n", " return min(numbers), max(numbers)\n",
"\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", "def cover(*integers) -> range:\n",
" \"\"\"A `range` that covers all the given integers, and any in between them.\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", " 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", " \"\"\"The sequence split into two pieces: (before position i, and i-and-after).\"\"\"\n",
" return sequence[:i], sequence[i:]\n", " return sequence[:i], sequence[i:]\n",
"\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", "def batched(data, n) -> list:\n",
" \"Batch data into lists of length n. The last batch may be shorter.\"\n", " \"Batch data into lists of length n. The last batch may be shorter.\"\n",
" # batched('ABCDEFG', 3) --> ABC DEF G\n", " # batched('ABCDEFG', 3) --> ABC DEF G\n",
@@ -284,68 +374,37 @@
" \"\"\"All length-n subsequences of sequence.\"\"\"\n", " \"\"\"All length-n subsequences of sequence.\"\"\"\n",
" return (sequence[i:i+n] for i in range(len(sequence) + 1 - n))\n", " return (sequence[i:i+n] for i in range(len(sequence) + 1 - n))\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", "\n",
"def is_int(x) -> bool: \"Is x an int?\"; return isinstance(x, int) \n", "def first_true(iterable, default=False):\n",
"\n", " \"\"\"Returns the first true value in the iterable.\n",
"def sign(x) -> int: \"0, +1, or -1\"; return (0 if x == 0 else +1 if x > 0 else -1)\n", " If no true value is found, returns `default`.\"\"\"\n",
"\n", " return next((x for x in iterable if x), default)"
"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", "cell_type": "markdown",
"def intersection(sets):\n", "metadata": {},
" \"Intersection of several sets.\"\n", "source": [
" first, *rest = sets\n", "More tests:"
" 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)"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 17, "execution_count": 9,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"## TESTS\n",
"\n",
"assert quantify(words('This is a test'), str.islower) == 3\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 dotproduct([1, 2, 3, 4], [1000, 100, 10, 1]) == 1234\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 list(flatten([{1, 2, 3}, (4, 5, 6), [7, 8, 9]])) == [1, 2, 3, 4, 5, 6, 7, 8, 9]\n",
"assert prod([2, 3, 5]) == 30\n", "assert append(([1, 2], [3, 4], [5, 6])) == [1, 2, 3, 4, 5, 6]\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 batched('abcdefghi', 3) == ['abc', 'def', 'ghi']\n", "assert batched('abcdefghi', 3) == ['abc', 'def', 'ghi']\n",
"assert list(sliding_window('abcdefghi', 3)) == ['abc', 'bcd', 'cde', 'def', 'efg', 'fgh', '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 first('abc') == 'a'\n",
"assert sign(-42) == -1 and sign(0) == 0 and sign(42) == +1\n", "assert first_true([0, None, False, 42, 99]) == 42"
"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'"
] ]
}, },
{ {
@@ -359,7 +418,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 10,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@@ -391,39 +450,21 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 16, "execution_count": 18,
"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,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"class Grid(dict):\n", "class Grid(dict):\n",
" \"\"\"A 2D grid, implemented as a mapping of {(x, y): cell_contents}.\"\"\"\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", " \"\"\"Initialize with either (e.g.) `Grid({(0, 0): 1, (1, 0): 2, ...})`, or\n",
" `Grid([(1, 2, 3), (4, 5, 6)]).\"\"\"\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", " self.update(mapping_or_rows if isinstance(mapping_or_rows, abc.Mapping) else\n",
" {(x, y): val \n", " {(x, y): val \n",
" for y, row in enumerate(mapping_or_rows) \n", " for y, row in enumerate(mapping_or_rows) \n",
" for x, val in enumerate(row)})\n", " for x, val in enumerate(row)})\n",
" self.width = max(map(X_, self)) + 1\n", "\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", " def copy(self): return Grid(self, directions=self.directions)\n",
" \n", " \n",
@@ -431,14 +472,43 @@
" \"\"\"Points on the grid that neighbor `point`.\"\"\"\n", " \"\"\"Points on the grid that neighbor `point`.\"\"\"\n",
" return [add(point, Δ) for Δ in self.directions if add(point, Δ) in self]\n", " return [add(point, Δ) for Δ in self.directions if add(point, Δ) in self]\n",
" \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", " \"\"\"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", " Xs = Xs or range(max(map(X_, self)) + 1)\n",
" for y in range(self.height)]\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", " \n",
" def to_picture(self, sep='', default='.') -> str:\n", " def to_picture(self, sep='', default='.', Xs=None, Ys=None) -> str:\n",
" \"\"\"The contents of the grid as a picture.\"\"\"\n", " \"\"\"The contents of the grid as a picture. Youi can specify the `Xs` and `Ys` to include.\"\"\"\n",
" return '\\n'.join(map(cat, self.to_rows(default)))" " 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", "cell_type": "code",
"execution_count": 18, "execution_count": 13,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@@ -480,7 +550,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 10, "execution_count": 14,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@@ -545,7 +615,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 11, "execution_count": 15,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [