From 368285e511147151dd68511647d20751eecdaa15 Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Thu, 16 Dec 2021 10:53:52 -0800 Subject: [PATCH] Add files via upload --- ipynb/Advent-2021.ipynb | 121 +++++++++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 34 deletions(-) diff --git a/ipynb/Advent-2021.ipynb b/ipynb/Advent-2021.ipynb index 57f17d8..be8483e 100644 --- a/ipynb/Advent-2021.ipynb +++ b/ipynb/Advent-2021.ipynb @@ -2439,43 +2439,75 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "With 250,000 points in the full map, I think it will take several minutes (not hours, not seconds), to find the optimal path with the `search_grid` algorithm. I really wish I had used A* search; I think it would run in just a few seconds." + "With 250,000 points in the full map, I estimated it would take several minutes to run the `search_grid` algorithm. I tested it, and it was 5 minutes. I wasn't satisfied with that, so I copied over the [A* search](https://en.wikipedia.org/wiki/A*_search_algorithm) from my [AoC 2017](https://github.com/norvig/pytudes/blob/main/ipynb/Advent%202017.ipynb) notebook, and supplied ity with the proper functions to make a move, compute the cost of a move, and estimate the distance to the goal (the `h_func` or \"heuristic function\"). A* is guaranteed to find an optimal path if the heuristic function never overestimates the cost from a state to the goal, so I use as an estimate the cost if every grid point along the way had a risk level of 1." ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 4min 21s, sys: 271 ms, total: 4min 22s\n", - "Wall time: 4min 22s\n" - ] - }, - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 65, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "%time answer(15.2, search_grid(repeat_grid(in15, 5)), 2957)" + "from heapq import heappop, heappush\n", + "\n", + "def Astar(start, moves_func, h_func, cost_func) -> Tuple[int, list]:\n", + " \"\"\"Find a (cost, path) tuple for the lowest-cost path from start to goal.\n", + " A goal is any state where h_func(s) == 0.\"\"\"\n", + " frontier = [(h_func(start), start)] # A priority queue, ordered by path_cost(s) + h(s)\n", + " previous = {start: None} # start state has no previous state; other states will\n", + " path_cost = {start: 0} # The cost of the best path to a state.\n", + " Path = lambda s: ([] if (s is None) else Path(previous[s]) + [s])\n", + " while frontier:\n", + " (f, s) = heappop(frontier)\n", + " if h_func(s) == 0:\n", + " return path_cost[s], Path(s)\n", + " for s2 in moves_func(s):\n", + " g = path_cost[s] + cost_func(s, s2)\n", + " if s2 not in path_cost or g < path_cost[s2]:\n", + " heappush(frontier, (g + h_func(s2), s2))\n", + " path_cost[s2] = g\n", + " previous[s2] = s\n", + " \n", + "def Astar_search_grid(grid):\n", + " \"\"\"The total cost of the best path from (0, 0) to bottom-right on grid.\"\"\"\n", + " start, goal = (0, 0), max(grid)\n", + " def moves_func(s): return grid.neighbors(s)\n", + " def h_func(s): return sum(goal) - sum(s)\n", + " def cost_func(s, s2): return grid[s2]\n", + " return Astar(start, moves_func, h_func, cost_func)[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With A* search the run time is greatly improved, from 5 minutes down to about 1 second." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 66, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "answer(15.2, Astar_search_grid(repeat_grid(in15, 5)), 2957)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "OK, 5 minutes wasn't so bad. I may come back and improve on this later (or maybe not).\n", - "\n", "Gary Grady's drawing represents the risk involved in finding a path that avoids bumping into the ceiling above or the chitons below.\n", "\n", "" @@ -2492,7 +2524,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 67, "metadata": {}, "outputs": [ { @@ -2513,14 +2545,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "- **Part 1:** The puzzle is to parse this hexadecimal transmission into data packets, according to the rules contained in [the instructions](https://adventofcode.com/2021/day/16), and add up all of the version numbers.\n", + "- **Part 1:** The puzzle is to parse this hexadecimal transmission into data packets, according to the rules contained in [the instructions](https://adventofcode.com/2021/day/16), and add up all of the version numbers of the packets.\n", "\n", - "The gist of [the instructions](https://adventofcode.com/2021/day/16) is to consider the hexadecimal sequence as a bit string, parse the bit string into bit fields, and construct nested packets based on the values of the fields. Here are basic types for `Bits` (bit string) and `Packet`, along with functions to convert from a hexadecimal string to a bit string, and from there to an int: " + "The gist of [the instructions](https://adventofcode.com/2021/day/16) is to consider the hexadecimal sequence as a bit string, divide the bit string into bit fields, and construct nested packets based on the values of the fields. Here are basic types for `Bits` (a bit string) and `Packet` (which contains a version umber `V`, a type ID `T`, and a `contents` field which can be either a number or a list of packets), along with functions to convert from a hexadecimal string to a bit string, and from there to an int: " ] }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 68, "metadata": {}, "outputs": [], "source": [ @@ -2529,6 +2561,7 @@ "\n", "def bits_from_hex(hex) -> Bits: \n", " \"\"\"Convert a hexadecimal string into a bit string, making sure each hex digit is 4 bits.\"\"\"\n", + " # I could have used just `bin(int(hex, 16))`, except that wouldn't left-zero-pad when needed.\n", " return cat(f'{int(x, 16):04b}' for x in hex)\n", "\n", "def int2(bits: Bits) -> int: \n", @@ -2536,6 +2569,26 @@ " return int(bits, 2)" ] }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0b11111111'" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bin(int('ff', 16))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -2545,7 +2598,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 70, "metadata": {}, "outputs": [], "source": [ @@ -2595,7 +2648,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 71, "metadata": {}, "outputs": [ { @@ -2604,7 +2657,7 @@ "True" ] }, - "execution_count": 69, + "execution_count": 71, "metadata": {}, "output_type": "execute_result" } @@ -2631,7 +2684,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 72, "metadata": {}, "outputs": [], "source": [ @@ -2667,7 +2720,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 73, "metadata": {}, "outputs": [ { @@ -2676,7 +2729,7 @@ "True" ] }, - "execution_count": 71, + "execution_count": 73, "metadata": {}, "output_type": "execute_result" }