Add files via upload

This commit is contained in:
Peter Norvig 2024-12-09 23:14:33 -08:00 committed by GitHub
parent 1778c1fdb2
commit c42268204b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 252 additions and 74 deletions

View File

@ -262,7 +262,7 @@
{
"data": {
"text/plain": [
"Puzzle 2.1: .0013 seconds, answer 257 ok"
"Puzzle 2.1: .0012 seconds, answer 257 ok"
]
},
"execution_count": 7,
@ -312,7 +312,7 @@
{
"data": {
"text/plain": [
"Puzzle 2.2: .0075 seconds, answer 328 ok"
"Puzzle 2.2: .0071 seconds, answer 328 ok"
]
},
"execution_count": 9,
@ -449,7 +449,7 @@
{
"data": {
"text/plain": [
"Puzzle 3.2: .0009 seconds, answer 75920122 ok"
"Puzzle 3.2: .0008 seconds, answer 75920122 ok"
]
},
"execution_count": 14,
@ -539,7 +539,7 @@
{
"data": {
"text/plain": [
"Puzzle 4.1: .0737 seconds, answer 2401 ok"
"Puzzle 4.1: .0704 seconds, answer 2401 ok"
]
},
"execution_count": 17,
@ -577,7 +577,7 @@
{
"data": {
"text/plain": [
"Puzzle 4.2: .0629 seconds, answer 1822 ok"
"Puzzle 4.2: .0602 seconds, answer 1822 ok"
]
},
"execution_count": 18,
@ -708,7 +708,7 @@
{
"data": {
"text/plain": [
"Puzzle 5.1: .0010 seconds, answer 5762 ok"
"Puzzle 5.1: .0014 seconds, answer 5762 ok"
]
},
"execution_count": 21,
@ -821,7 +821,7 @@
"source": [
"# [Day 6](https://adventofcode.com/2024/day/6): Guard Gallivant\n",
"\n",
"Today's input is a 2D map of the manufacturing lab, with \".\" indicating an empty space, \"#\" indicating an obstruction, and \"^\" indicating the position of the security guard."
"Today's input is a 2D map of the manufacturing lab, with \"`.`\" indicating an empty space, \"`#`\" indicating an obstruction, and \"`^`\" indicating the position of the security guard."
]
},
{
@ -875,7 +875,7 @@
{
"data": {
"text/plain": [
"Puzzle 6.1: .0045 seconds, answer 5329 ok"
"Puzzle 6.1: .0047 seconds, answer 5329 ok"
]
},
"execution_count": 26,
@ -912,52 +912,51 @@
"- An obstacle position must be somewhere on the guard's path, otherwise it would have no effect.\n",
"- The instructions say it can't be the guard's initial position.\n",
"- A loop is when the guard's path returns to the same position with the same facing. This suggests that my Part 1 solution was not completely helpful: to find duplicate positions in the path I would need a set of position/facing pairs, not just positions.\n",
"- Alternatively, I note that the grid is only of size 130 x 130; I could declare a loop after 130<sup>2</sup> steps; that seems simpler. \n",
"- The simplest approach for finding obstacles is to test each point on the path, one at a time.\n",
"- There are 5,329 such points, so the runtime should be about 5,000 times longer than Part 1; on the order of 10 seconds. I'll try it, and if it seems too slow, I'll try to think of something else."
"- Alternatively, any path that has taken a number of steps equal to the number of empty spaces in the grid must be a loop. That seems simpler. \n",
"- The simplest approach for finding obstacle positions is to temporarily place an obstacle on each point on the path, one at a time, and see if it leads to a loop.\n",
"- There are 5,329 positions on the path, so the runtime should be about 5,000 times longer than Part 1; on the order of 20 seconds or so. I'll try it, and if it seems too slow, I'll try to think of something else."
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "fb992592-1b35-47c1-a1dc-8bb0c0653d6d",
"id": "1718fecb-aa3e-4162-9948-1c06d4ec5e8a",
"metadata": {},
"outputs": [],
"source": [
"def is_loopy_path(grid: Grid, guard_pos, facing=North) -> bool:\n",
" \"\"\"Does the path followed by the guard form a loop?\"\"\"\n",
" for step in range(prod(grid.size)):\n",
" N = len(lab_grid.findall('.')) # Number of empty spaces\n",
" for step in range(N):\n",
" ahead = add2(guard_pos, facing)\n",
" if ahead not in grid:\n",
" return False # Not a loop\n",
" return False # Walked off the grid; not a loop\n",
" elif grid[ahead] == '#':\n",
" facing = make_turn(facing, 'R')\n",
" else:\n",
" guard_pos = ahead\n",
" return True # Ran too many steps; must be a loop\n",
" \n",
"def find_loopy_obstacles(grid: Grid, guard='^', facing=North) -> Set[Point]:\n",
"def find_loopy_obstacles(grid: Grid) -> Iterable[Point]:\n",
" \"\"\"All positions in which placing an obstacle would result in a loopy path for the guard.\"\"\"\n",
" guard_pos = the(grid.findall(guard))\n",
" obstacle_positions = set()\n",
" guard_pos = the(grid.findall('^'))\n",
" for pos in set(follow_path(grid)) - {guard_pos}:\n",
" grid[pos] = '#' # Place obstacle \n",
" grid[pos] = '#' # Temporarily place an obstacle \n",
" if is_loopy_path(grid, guard_pos):\n",
" obstacle_positions.add(pos)\n",
" grid[pos] = '.' # Remove obstacle\n",
" return obstacle_positions"
" yield pos\n",
" grid[pos] = '.' # Remove the obstacle"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "0dfcbee9-7cfd-417c-8bd2-14544f0a100b",
"id": "36196264-eb33-4fc0-95d5-06c985105ebf",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 6.2: 14.1021 seconds, answer 2162 ok"
"Puzzle 6.2: 18.6630 seconds, answer 2162 ok"
]
},
"execution_count": 28,
@ -967,7 +966,7 @@
],
"source": [
"answer(6.2, 2162, lambda:\n",
" len(find_loopy_obstacles(lab_grid)))"
" quantify(find_loopy_obstacles(lab_grid)))"
]
},
{
@ -1067,7 +1066,7 @@
"id": "e0d9b0b2-fe1e-434e-b84e-c044da3d3673",
"metadata": {},
"source": [
"No; with 13 numbers on a line there are 11 places to choose an operator, and 2<sup>11</sup> = 2048; a small number. I'll define `can_be_calibrated` to keep a set of `results`, updating the set for each new number and each possible operator."
"No problem! With 13 numbers on a line there are 11 places to choose an operator, and 2<sup>11</sup> = 2048; a small number. I'll define `can_be_calibrated` to keep a set of `results`, updating the set for each new number and each possible operator."
]
},
{
@ -1095,7 +1094,7 @@
{
"data": {
"text/plain": [
"Puzzle 7.1: .0518 seconds, answer 1985268524462 ok"
"Puzzle 7.1: .0471 seconds, answer 1985268524462 ok"
]
},
"execution_count": 32,
@ -1115,7 +1114,7 @@
"source": [
"### Part 2: What is the total calibration result of possibly true equations, allowing concatenation?\n",
"\n",
"In Part 2, the equation \"192: 17 8 14\" can be balanced by using a concatenate operator, \"192 = ((17 || 8) + 14)\". With three operators, the equation with 11 operators now has 3<sup>11</sup> = 177,147, almost 100 times more than Part 1, so this will take a few seconds:"
"In Part 2, the equation \"`192: 17 8 14`\" can be balanced by using a concatenate operator, \"`192 = ((17 || 8) + 14)`\". With three operators, the equation with 11 operators now has 3<sup>11</sup> = 177,147, almost 100 times more than Part 1, so this will take a few seconds:"
]
},
{
@ -1127,7 +1126,7 @@
{
"data": {
"text/plain": [
"Puzzle 7.2: 6.1540 seconds, answer 150077710195188 ok"
"Puzzle 7.2: 5.1590 seconds, answer 150077710195188 ok"
]
},
"execution_count": 33,
@ -1202,7 +1201,7 @@
{
"data": {
"text/plain": [
"Puzzle 8.1: .0014 seconds, answer 220 ok"
"Puzzle 8.1: .0009 seconds, answer 220 ok"
]
},
"execution_count": 35,
@ -1240,7 +1239,7 @@
"id": "ff79d605-813a-46ac-8473-a1198be0e99f",
"metadata": {},
"source": [
"### How many unique locations within the bounds of the map contain an updated antinode?\n",
"### Part 2: How many unique locations within the bounds of the map contain an updated antinode?\n",
"\n",
"For Part 2, the updated definition of antinodes means that they can now occur at any point that is exactly on line with two antennas of the same frequency, regardless of distance. So if the two antennas are *A* and *B* then the antinbodal points are of the form *A* + *k*(*A* - *B*) for any scalar value *k* (positive or negative) that makes the resulting point fall within the grid."
]
@ -1283,7 +1282,7 @@
{
"data": {
"text/plain": [
"Puzzle 8.2: .0029 seconds, answer 813 ok"
"Puzzle 8.2: .0021 seconds, answer 813 ok"
]
},
"execution_count": 37,
@ -1399,7 +1398,7 @@
{
"data": {
"text/plain": [
"Puzzle 9.1: .0520 seconds, answer 6332189866718 ok"
"Puzzle 9.1: .0463 seconds, answer 6332189866718 ok"
]
},
"execution_count": 40,
@ -1480,7 +1479,7 @@
{
"data": {
"text/plain": [
"Puzzle 9.2: 6.7720 seconds, answer 6353648390778 ok"
"Puzzle 9.2: 6.3337 seconds, answer 6353648390778 ok"
]
},
"execution_count": 42,
@ -1501,6 +1500,166 @@
"I got the right answer, but I confess I had an off-by-one **bug** in `find_freespace` on the first try."
]
},
{
"cell_type": "markdown",
"id": "7a900425-fe22-4d2f-8d1d-46c319c109e9",
"metadata": {},
"source": [
"# [Day 10](https://adventofcode.com/2024/day/10): Hoof It\n",
"\n",
"Today's input is a topological map, with digits indicating the elevation of each terrain position."
]
},
{
"cell_type": "code",
"execution_count": 43,
"id": "5804fb03-05f3-402f-b6cc-6804c5d22512",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"────────────────────────────────────────────────────────────────────────────────────────────────────\n",
"Puzzle input ➜ 60 strs:\n",
"────────────────────────────────────────────────────────────────────────────────────────────────────\n",
"432109865210212123765432101234321098543289654320132112121058\n",
"045678774324301012892343023445456787650198763013241001034569\n",
"187678789465692321001056014896234986456787012894653212123678\n",
"296589921056789433217837895687145675323891233765784589238987\n",
"345437835434576544786921278761010014210710321212098676521067\n",
"032126546323465435695430789760121223121653450303145125430678\n",
"123010567810156543212345699859834321056544067654236012321589\n",
"543213498987657665401030787348765430187432198765987622345432\n",
"...\n",
"────────────────────────────────────────────────────────────────────────────────────────────────────\n",
"Parsed representation ➜ 60 tuples:\n",
"────────────────────────────────────────────────────────────────────────────────────────────────────\n",
"(4, 3, 2, 1, 0, 9, 8, 6, 5, 2, 1, 0, 2, 1, 2, 1, 2, 3, 7, 6, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, ...\n",
"(0, 4, 5, 6, 7, 8, 7, 7, 4, 3, 2, 4, 3, 0, 1, 0, 1, 2, 8, 9, 2, 3, 4, 3, 0, 2, 3, 4, 4, 5, 4, 5, ...\n",
"(1, 8, 7, 6, 7, 8, 7, 8, 9, 4, 6, 5, 6, 9, 2, 3, 2, 1, 0, 0, 1, 0, 5, 6, 0, 1, 4, 8, 9, 6, 2, 3, ...\n",
"(2, 9, 6, 5, 8, 9, 9, 2, 1, 0, 5, 6, 7, 8, 9, 4, 3, 3, 2, 1, 7, 8, 3, 7, 8, 9, 5, 6, 8, 7, 1, 4, ...\n",
"(3, 4, 5, 4, 3, 7, 8, 3, 5, 4, 3, 4, 5, 7, 6, 5, 4, 4, 7, 8, 6, 9, 2, 1, 2, 7, 8, 7, 6, 1, 0, 1, ...\n",
"(0, 3, 2, 1, 2, 6, 5, 4, 6, 3, 2, 3, 4, 6, 5, 4, 3, 5, 6, 9, 5, 4, 3, 0, 7, 8, 9, 7, 6, 0, 1, 2, ...\n",
"(1, 2, 3, 0, 1, 0, 5, 6, 7, 8, 1, 0, 1, 5, 6, 5, 4, 3, 2, 1, 2, 3, 4, 5, 6, 9, 9, 8, 5, 9, 8, 3, ...\n",
"(5, 4, 3, 2, 1, 3, 4, 9, 8, 9, 8, 7, 6, 5, 7, 6, 6, 5, 4, 0, 1, 0, 3, 0, 7, 8, 7, 3, 4, 8, 7, 6, ...\n",
"...\n"
]
}
],
"source": [
"topo = Grid(parse(10, digits))"
]
},
{
"cell_type": "markdown",
"id": "d951807a-3611-445f-84ee-352221a25968",
"metadata": {},
"source": [
"### Part 1: What is the sum of the scores of all trailheads on your topographic map?\n",
"\n",
"A **trailhead** is any position with elevation 0, and a **peak** is any position with elevation 9. The **score** of a trailhead is the number of peaks that can be reached by following a path where each step increases the elevation by exactly 1. All moves are in one of the four cardinal directions (north/south/east/west).\n",
"\n",
"I'll keep a set of points on the frontier of possible paths, updating this set on each iteratation from 1 to 9, by looking at each point on the frontier and seeing which of the neighboring points `p` have the right elevation:"
]
},
{
"cell_type": "code",
"execution_count": 44,
"id": "76b5379e-ee19-4607-91b8-88ec7b38023f",
"metadata": {},
"outputs": [],
"source": [
"def score(topo: Grid, trailhead: Point) -> int:\n",
" \"\"\"How many peaks can be reached from this trailhead?\"\"\"\n",
" frontier = {trailhead}\n",
" for elevation in range(1, 10):\n",
" frontier = {p for p in union(map(topo.neighbors, frontier))\n",
" if topo[p] == elevation}\n",
" return len(frontier)"
]
},
{
"cell_type": "code",
"execution_count": 45,
"id": "97cf05f7-fa56-4a90-b2d8-2cd4d9b81f95",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 10.1: .0153 seconds, answer 744 ok"
]
},
"execution_count": 45,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"answer(10.1, 744, lambda:\n",
" sum(score(topo, head) for head in topo.findall([0])))"
]
},
{
"cell_type": "markdown",
"id": "4656eb08-b12a-4a02-92b8-ac23f2361387",
"metadata": {},
"source": [
"### Part 2: What is the sum of the ratings of all trailheads?\n",
"\n",
"The **rating** of a trailhead is the number of distinct paths from the trailhead to a peak.\n",
"\n",
"As in Part 1, I'll keep a frontier and update it on each iteration from 1 to 9, but this time the frontier will be a counter of `{position: count}` where the count indicates the number of paths to that position. On each iteration I'll look at each point `f` on the frontier and see which of the neighboring points `p` have the right elevation, and increment the counts for those points by the count for `f`:"
]
},
{
"cell_type": "code",
"execution_count": 46,
"id": "b763450f-a565-4936-bee4-e531c2eeebdb",
"metadata": {},
"outputs": [],
"source": [
"def rating(topo: Grid, trailhead: Point) -> int:\n",
" \"\"\"How many distinct paths are there from this trailhead to any peak?\"\"\"\n",
" frontier = Counter({trailhead: 1})\n",
" for elevation in range(1, 10):\n",
" frontier = accumulate((p, frontier[f]) \n",
" for f in frontier\n",
" for p in topo.neighbors(f) if topo[p] == elevation)\n",
" return sum(frontier.values())"
]
},
{
"cell_type": "code",
"execution_count": 47,
"id": "f8a87032-6556-4fc9-9bb8-573611aee8dc",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Puzzle 10.2: .0175 seconds, answer 1651 ok"
]
},
"execution_count": 47,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"answer(10.2, 1651, lambda:\n",
" sum(rating(topo, head) for head in topo.findall([0])))"
]
},
{
"cell_type": "markdown",
"id": "af410d30-7096-4be6-bb20-904b3c8e2f59",
"metadata": {},
"source": [
"Today I went pretty fast (for me); I started a few minutes late and finished in 15 minutes. From the point of view of a competitive coder I did foolish things like write docstrings and use variables of more than one letter, so while this time was fast for me, it placed out of the top 1000."
]
},
{
"cell_type": "markdown",
"id": "c3317844-2b4a-4756-8a59-b765aa467445",
@ -1513,7 +1672,7 @@
},
{
"cell_type": "code",
"execution_count": 43,
"execution_count": 48,
"id": "34813fc9-a000-4cd8-88ae-692851b3242c",
"metadata": {},
"outputs": [
@ -1523,22 +1682,24 @@
"text": [
"Puzzle 1.1: .0002 seconds, answer 1830467 ok\n",
"Puzzle 1.2: .0002 seconds, answer 26674158 ok\n",
"Puzzle 2.1: .0013 seconds, answer 257 ok\n",
"Puzzle 2.2: .0075 seconds, answer 328 ok\n",
"Puzzle 2.1: .0012 seconds, answer 257 ok\n",
"Puzzle 2.2: .0071 seconds, answer 328 ok\n",
"Puzzle 3.1: .0013 seconds, answer 156388521 ok\n",
"Puzzle 3.2: .0009 seconds, answer 75920122 ok\n",
"Puzzle 4.1: .0737 seconds, answer 2401 ok\n",
"Puzzle 4.2: .0629 seconds, answer 1822 ok\n",
"Puzzle 5.1: .0010 seconds, answer 5762 ok\n",
"Puzzle 3.2: .0008 seconds, answer 75920122 ok\n",
"Puzzle 4.1: .0704 seconds, answer 2401 ok\n",
"Puzzle 4.2: .0602 seconds, answer 1822 ok\n",
"Puzzle 5.1: .0014 seconds, answer 5762 ok\n",
"Puzzle 5.2: .0020 seconds, answer 4130 ok\n",
"Puzzle 6.1: .0045 seconds, answer 5329 ok\n",
"Puzzle 6.2: 14.1021 seconds, answer 2162 ok\n",
"Puzzle 7.1: .0518 seconds, answer 1985268524462 ok\n",
"Puzzle 7.2: 6.1540 seconds, answer 150077710195188 ok\n",
"Puzzle 8.1: .0014 seconds, answer 220 ok\n",
"Puzzle 8.2: .0029 seconds, answer 813 ok\n",
"Puzzle 9.1: .0520 seconds, answer 6332189866718 ok\n",
"Puzzle 9.2: 6.7720 seconds, answer 6353648390778 ok\n"
"Puzzle 6.1: .0047 seconds, answer 5329 ok\n",
"Puzzle 6.2: 18.6630 seconds, answer 2162 ok\n",
"Puzzle 7.1: .0471 seconds, answer 1985268524462 ok\n",
"Puzzle 7.2: 5.1590 seconds, answer 150077710195188 ok\n",
"Puzzle 8.1: .0009 seconds, answer 220 ok\n",
"Puzzle 8.2: .0021 seconds, answer 813 ok\n",
"Puzzle 9.1: .0463 seconds, answer 6332189866718 ok\n",
"Puzzle 9.2: 6.3337 seconds, answer 6353648390778 ok\n",
"Puzzle 10.1: .0153 seconds, answer 744 ok\n",
"Puzzle 10.2: .0175 seconds, answer 1651 ok\n"
]
}
],

View File

@ -15,7 +15,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
@ -67,7 +67,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
@ -120,12 +120,13 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"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",
"Ints = Sequence[int]\n",
"\n",
"def ints(text: str) -> Tuple[int]:\n",
" \"\"\"A tuple of all the integers in text, ignoring non-number characters.\"\"\"\n",
@ -167,20 +168,16 @@
},
{
"cell_type": "code",
"execution_count": 105,
"execution_count": null,
"metadata": {},
"outputs": [
"outputs": [],
"source": []
},
{
"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"
]
}
],
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"answers = {} # `answers` is a dict of {puzzle_number: answer}\n",
"\n",
@ -189,7 +186,7 @@
"class answer:\n",
" \"\"\"Verify that calling `code` computes the `solution` to `puzzle`. \n",
" Record results in the dict `answers`.\"\"\"\n",
" def __init__(self, puzzle: float, solution, code:callable=lambda:unknown):\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",
@ -228,9 +225,21 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 1,
"metadata": {},
"outputs": [],
"outputs": [
{
"ename": "NameError",
"evalue": "name 'defaultdict' is not defined",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[1], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mclass\u001b[39;00m \u001b[38;5;21;01mmultimap\u001b[39;00m(\u001b[43mdefaultdict\u001b[49m):\n\u001b[1;32m 2\u001b[0m \u001b[38;5;124;03m\"\"\"A mapping of {key: [val1, val2, ...]}.\"\"\"\u001b[39;00m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m, pairs:Iterable[\u001b[38;5;28mtuple\u001b[39m]\u001b[38;5;241m=\u001b[39m(), symmetric\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m):\n",
"\u001b[0;31mNameError\u001b[0m: name 'defaultdict' is not defined"
]
}
],
"source": [
"class multimap(defaultdict):\n",
" \"\"\"A mapping of {key: [val1, val2, ...]}.\"\"\"\n",
@ -295,6 +304,13 @@
" first, *rest = sets\n",
" return set(first).intersection(*rest)\n",
"\n",
"def accumulate(item_count_pairs: Iterable[Tuple[object, int]]) -> Counter:\n",
" \"\"\"Add up all the (item, count) pairs into a Counter.\"\"\"\n",
" counter = Counter()\n",
" for (item, count) in item_count_pairs:\n",
" counter[item] += count\n",
" return counter\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",
@ -422,7 +438,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
@ -446,10 +462,10 @@
"def Ys(points) -> Tuple[int]: \"Y coordinates of a collection of points\"; return mapt(Y_, points)\n",
"def Zs(points) -> Tuple[int]: \"X coordinates of a collection of points\"; return mapt(Z_, points)\n",
"\n",
"def add(p: Point, q: Point) -> Point: return mapt(operator.add, p, q)\n",
"def sub(p: Point, q: Point) -> Point: return mapt(operator.sub, p, q)\n",
"def neg(p: Point) -> Vector: return mapt(operator.neg, p)\n",
"def mul(p: Point, k: float) -> Vector: return tuple(k * c for c in p)\n",
"def add(p: Point, q: Point) -> Point: \"Add points\"; return mapt(operator.add, p, q)\n",
"def sub(p: Point, q: Point) -> Point: \"Subtract points\"; return mapt(operator.sub, p, q)\n",
"def neg(p: Point) -> Vector: \"Negate a point\"; return mapt(operator.neg, p)\n",
"def mul(p: Point, k: float) -> Vector: \"Scalar multiply\"; return tuple(k * c for c in p)\n",
"\n",
"def distance(p: Point, q: Point) -> float:\n",
" \"\"\"Euclidean (L2) distance between two points.\"\"\"\n",
@ -745,15 +761,16 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 113,
"metadata": {},
"outputs": [],
"source": [
"class Graph(dict):\n",
"class Graph(defaultdict):\n",
" \"\"\"A graph of {node: [neighboring_nodes...]}. \n",
" Can store other kwd attributes on it (which you can't do with a dict).\"\"\"\n",
" def __init__(self, contents, **kwds):\n",
" self.update(contents)\n",
" self.default_factory = list\n",
" self.__dict__.update(**kwds)"
]
},