diff --git a/ipynb/Advent-2016.ipynb b/ipynb/Advent-2016.ipynb index f730a82..37ef084 100644 --- a/ipynb/Advent-2016.ipynb +++ b/ipynb/Advent-2016.ipynb @@ -20,7 +20,7 @@ "\n", "- I'll import my favorite modules and functions, so I don't have to do it each day.\n", "- From looking at [last year's](http://adventofcode.com/2015) puzzles, I knew that there would be a data file on many days, so I defined the function `Input` to open the file.\n", - "- I wanted a function `answer` to record the correct answer and its run time.\n", + "- I wanted a functionto record the correct `answer` and its run time.\n", "- From working on another puzzle site, [Project Euler](https://projecteuler.net/), I had built up a collection of utility functions, shown below:" ] }, @@ -35,6 +35,8 @@ }, "outputs": [], "source": [ + "#### Imports\n", + "\n", "import re\n", "import numpy as np\n", "import math\n", @@ -42,21 +44,19 @@ "\n", "from collections import Counter, defaultdict, namedtuple, deque\n", "from functools import lru_cache, cache\n", - "from itertools import permutations, combinations, chain, cycle, product, islice\n", + "from itertools import permutations, combinations, combinations_with_replacement, chain, cycle, product, islice\n", "from heapq import heappop, heappush\n", "from statistics import mean, median\n", "from typing import Iterable\n", "\n", + "#### Daily `Input` and `answer`\n", + "\n", "def Input(day: int) -> Iterable[str]:\n", " \"\"\"Open this day's input file.\"\"\"\n", " return open(f'AOC/2016/input{day}.txt')\n", "\n", "answers = {} # `answers` is a dict of {puzzle_number: answer}\n", "\n", - "def ints(text: str) -> list[int]: \n", - " \"All the integers anywhere in text.\"\n", - " return [int(x) for x in re.findall(r'\\d+', text)]\n", - "\n", "class answer:\n", " \"\"\"Verify that calling `code` computes the `solution` to `puzzle`. \n", " Record results in the dict `answers`.\"\"\"\n", @@ -72,21 +72,23 @@ " \"\"\"The repr of an answer shows what happened.\"\"\"\n", " correct = 'correct' if self.ok else 'WRONG!!'\n", " expected = '' if self.ok else f'; EXPECTED: {self.solution}'\n", - " return f'Puzzle {self.puzzle:4.1f}: {time_str(self.msecs)}, {correct} answer: {self.got:<15}{expected}'\n", - "\n", - "def time_str(msecs: float) -> str:\n", - " \"\"\"Turn a number of milliseconds into a printable string.\"\"\"\n", - " return f'{msecs:8,.1f} msecs' if msecs < 60_000 else f'{msecs/60_000:6,.1f} MINUTES'\n", + " return f'Puzzle {self.puzzle:4.1f}: {self.msecs:8,.1f} milliseconds, {correct} answer: {self.got:<15}{expected}'\n", "\n", "def summary(answers: dict):\n", " \"\"\"Summary report on the answers.\"\"\"\n", " times = [answer.msecs for answer in answers.values()]\n", - " def stat(fn, times): return f'{fn.__name__} = {time_str(fn(times)).strip()}' \n", + " def stat(fn, times): return f'{fn.__name__} = {fn(times):,.1f}' \n", " stats = [stat(fn, times) for fn in (sum, mean, median, max)]\n", - " print(f'Time: {\", \".join(stats)}\\n')\n", + " print(f'Time in milliseconds: {\", \".join(stats)}\\n')\n", " for day in sorted(answers):\n", " print(answers[day])\n", "\n", + "#### Utility Functions\n", + "\n", + "def ints(text: str) -> list[int]: \n", + " \"All the integers anywhere in text.\"\n", + " return [int(x) for x in re.findall(r'\\d+', text)]\n", + " \n", "def transpose(matrix): \n", " \"\"\"Transpose of a list of lists.\"\"\"\n", " return zip(*matrix)\n", @@ -121,6 +123,16 @@ " for c in combinations(items, r):\n", " yield c\n", "\n", + "def trace1(f):\n", + " \"Decorator to print a trace of the input and output of a function on one line.\"\n", + " def traced_f(*args):\n", + " result = f(*args)\n", + " print('{}({}) = {}'.format(f.__name__, ', '.join(map(str, args)), result))\n", + " return result\n", + " return traced_f\n", + "\n", + "#### 2D (x, y) Points\n", + "\n", "# 2-D points implemented using (x, y) tuples\n", "def X(point) -> int: return point[0]\n", "def Y(point) -> int: return point[1]\n", @@ -136,17 +148,11 @@ " return ((x+1, y), (x-1, y), (x, y+1), (x, y-1),\n", " (x+1, y+1), (x-1, y-1), (x+1, y-1), (x-1, y+1))\n", "\n", - "def cityblock_distance(p, q=(0, 0)): \n", + "def cityblock_distance(p, q=(0, 0)) -> int: \n", " \"City block distance between two points.\"\n", " return abs(X(p) - X(q)) + abs(Y(p) - Y(q))\n", "\n", - "def trace1(f):\n", - " \"Decorator to print a trace of the input and output of a function on one line.\"\n", - " def traced_f(*args):\n", - " result = f(*args)\n", - " print('{}({}) = {}'.format(f.__name__, ', '.join(map(str, args)), result))\n", - " return result\n", - " return traced_f\n", + "#### A* Search\n", "\n", "def astar_search(start, h_func, moves_func):\n", " \"Find a shortest sequence of states from start to a goal state (a state s with h_func(s) == 0).\"\n", @@ -163,36 +169,17 @@ " heappush(frontier, (new_cost + h_func(s2), s2))\n", " path_cost[s2] = new_cost\n", " previous[s2] = s\n", - " return dict(fail=True, front=len(frontier), prev=len(previous))\n", + " return None\n", " \n", "def Path(previous, s) -> list: \n", " \"Return a list of states that lead to state s, according to the previous dict.\"\n", - " return ([] if (s is None) else Path(previous, previous[s]) + [s])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some tests/examples for these:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "assert tuple(transpose(((1, 2, 3), (4, 5, 6)))) == ((1, 4), (2, 5), (3, 6))\n", - "assert first('abc') == first(['a', 'b', 'c']) == 'a'\n", - "assert cat(['a', 'b', 'c']) == 'abc'\n", - "assert (groupby(['test', 'one', 'two', 'three', 'four'], key=len) \n", - " == {3: ['one', 'two'], 4: ['test', 'four'], 5: ['three']})" + " return ([] if (s is None) else Path(previous, previous[s]) + [s])\n", + "\n", + "def astar_step_count(*args) -> int:\n", + " \"\"\"The number of steps to get to the goal.\n", + " (the length of the path, not counting the starting state).\"\"\"\n", + " path = astar_search(*args)\n", + " return len(path) - 1" ] }, { @@ -202,61 +189,46 @@ "# [Day 1](http://adventofcode.com/2016/day/1): No Time for a Taxicab\n", "\n", "Given a sequence of moves, such as `\"R2, L3\"`, which means turn 90° to the right and go forward 2 blocks, then turn 90° left and go 3 blocks, how many blocks do we end up away from the start? I make the following choices:\n", - "* **Intersection Points** in the city grid will be represented as points on the complex plane. \n", - "* **Headings and turns** can be represented by unit vectors in the complex plane: if you are heading east (along the positive real axis), then a left turn means you head north, and a right turn means you head south, and [in general](https://betterexplained.com/articles/understanding-why-complex-multiplication-works/) a left or right turn is a multiplication of your current heading by the `North` or `South` unit vectors, respectively. \n", - "* **Moves** of the form `\"R53\"` will be parsed into a `(turn, distance)` pair, e.g. `(South, 53)`.\n", + "* **Intersection Points** in the city grid will be represented as `(x, y)` pairs.\n", + "* **Headings and turns** are represented by unit vectors in a `headings` list, and an index into the list that gets updated. \n", + "* **Moves** of the form `\"R20\"` will be parsed as `(+1, 20)` and `\"L30\"` as `(-1, 30)`.\n", "\n", "To solve the puzzle with the function `how_far(moves)`, I initialize the starting location as the origin and the starting heading as North, and follow the list of moves, updating the heading and location on each step, before returning the distance from the final location to the origin." ] }, { "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, + "execution_count": 2, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 1.1: 0.5 msecs, correct answer: 262.0 " + "Puzzle 1.1: 0.4 milliseconds, correct answer: 262 " ] }, - "execution_count": 3, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "Point = complex \n", - "N, S, E, W = 1j, -1j, 1, -1 # Unit vectors for headings\n", - "\n", - "def distance(point) -> float: \n", - " \"City block distance between point and the origin.\"\n", - " return abs(point.real) + abs(point.imag)\n", - "\n", - "def how_far(moves) -> float:\n", - " \"After following moves, how far away from the origin do we end up?\"\n", - " loc, heading = 0, N # Begin at origin, heading North\n", - " for (turn, dist) in parse_moves(moves):\n", - " heading *= turn\n", - " loc += heading * dist\n", - " return distance(loc)\n", + "def how_far(text: str) -> int:\n", + " \"\"\"After following moves, how far away from the origin do we end up?\"\"\"\n", + " h, x, y, = 0, 0, 0 # Begin at origin, heading North\n", + " for (turn, dist) in parse_moves(text):\n", + " h = (h + turn) % 4\n", + " x += X(headings[h]) * dist\n", + " y += Y(headings[h]) * dist\n", + " return cityblock_distance((x, y))\n", "\n", "def parse_moves(text: str) -> list[tuple]:\n", - " \"Return a list of (turn, distance) pairs from text of form 'R2, L42, ...'\"\n", - " turns = dict(L=N, R=S)\n", + " \"\"\"Return a list of pairs: from text of form \"R2, L42, ...\", return [(+1, 2), (-1, 42), ...].\"\"\"\n", + " turns = dict(R=+1, L=-1)\n", " return [(turns[RL], int(d))\n", " for (RL, d) in re.findall(r'(R|L)(\\d+)', text)]\n", "\n", - "assert distance(Point(3, 4)) == 7 # City block distance; Euclidean distance would be 5\n", - "assert parse_moves('R2, L42') == [(S, 2), (N, 42)]\n", - "assert how_far(\"R2, L3\") == 5\n", - "assert how_far(\"R2, R2, R2\") == 2\n", - "assert how_far(\"R5, L5, R5, R3\") == 12\n", + "headings = [(0, 1), (1, 0), (0, -1), (-1, 0)] # North, East, South, West\n", "\n", "answer(1.1, 262, lambda:\n", " how_far(Input(1).read()))" @@ -266,46 +238,40 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In **part two** of this puzzle, I have to find the first point that is visited twice. To support that, I keep track of the set of visited points. My first submission was wrong, because I didn't consider that the first point visited twice might be in the middle of a move, not the end, so I added the \"`for i`\" loop to iterate over the path of a move, one point at a time." + "In **part two** of this puzzle, I have to find the first point that is visited twice. To support that, I keep track of the set of visited points. My first submission was wrong, because I didn't consider that the first point visited twice might be in the middle of a move, not the end, so I added the \"`for`\" loop to iterate over the path of a move, one point at a time." ] }, { "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, + "execution_count": 3, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 1.2: 0.6 msecs, correct answer: 131.0 " + "Puzzle 1.2: 0.3 milliseconds, correct answer: 131 " ] }, - "execution_count": 4, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "def visited_twice(text) -> float:\n", - " \"Following moves in text, find the first location we visit twice, and return the distance to it.\"\n", - " loc, heading = 0, N # Begin at origin, heading North\n", - " visited = {loc}\n", + "def visited_twice(text: str) -> int:\n", + " \"\"\"Following moves, find the first location we visit twice, and return the distance to it.\"\"\"\n", + " headings = [(0, 1), (1, 0), (0, -1), (-1, 0)] # N, E, S, W\n", + " x, y, h = 0, 0, 0 # Begin at origin, heading North\n", + " visited = {(x, y)}\n", " for (turn, dist) in parse_moves(text):\n", - " heading *= turn\n", - " for i in range(dist):\n", - " loc += heading\n", - " if loc in visited:\n", - " return distance(loc)\n", - " visited.add(loc)\n", - "\n", - "assert visited_twice(\"R8, R4, R4, R8\") == 4\n", - "assert visited_twice(\"R8, R4, R4, L8\") == None\n", - "assert visited_twice(\"R8, R0, R1\") == 7\n", + " h = (h + turn) % 4\n", + " for _ in range(dist):\n", + " x += X(headings[h])\n", + " y += Y(headings[h])\n", + " if (x, y) in visited:\n", + " return cityblock_distance((x, y))\n", + " else:\n", + " visited.add((x, y))\n", "\n", "answer(1.2, 131, lambda:\n", " visited_twice(Input(1).read()))" @@ -325,7 +291,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "collapsed": false, "jupyter": { @@ -336,10 +302,10 @@ { "data": { "text/plain": [ - "Puzzle 2.1: 0.6 msecs, correct answer: 14894 " + "Puzzle 2.1: 0.5 milliseconds, correct answer: 14894 " ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -393,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "collapsed": false, "jupyter": { @@ -404,10 +370,10 @@ { "data": { "text/plain": [ - "Puzzle 2.2: 0.5 msecs, correct answer: 26B96 " + "Puzzle 2.2: 0.4 milliseconds, correct answer: 26B96 " ] }, - "execution_count": 6, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -440,7 +406,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "collapsed": false, "jupyter": { @@ -451,10 +417,10 @@ { "data": { "text/plain": [ - "Puzzle 3.1: 0.2 msecs, correct answer: 869 " + "Puzzle 3.1: 0.2 milliseconds, correct answer: 869 " ] }, - "execution_count": 7, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -498,7 +464,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": { "collapsed": false, "jupyter": { @@ -509,10 +475,10 @@ { "data": { "text/plain": [ - "Puzzle 3.2: 0.4 msecs, correct answer: 1544 " + "Puzzle 3.2: 0.4 milliseconds, correct answer: 1544 " ] }, - "execution_count": 8, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -538,7 +504,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": { "collapsed": false, "jupyter": { @@ -549,10 +515,10 @@ { "data": { "text/plain": [ - "Puzzle 4.1: 4.0 msecs, correct answer: 409147 " + "Puzzle 4.1: 4.1 milliseconds, correct answer: 409147 " ] }, - "execution_count": 9, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -593,7 +559,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": { "collapsed": false, "jupyter": { @@ -604,10 +570,10 @@ { "data": { "text/plain": [ - "Puzzle 4.2: 1.6 msecs, correct answer: 991 " + "Puzzle 4.2: 1.7 milliseconds, correct answer: 991 " ] }, - "execution_count": 10, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -642,7 +608,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": { "collapsed": false, "jupyter": { @@ -667,10 +633,10 @@ { "data": { "text/plain": [ - "Puzzle 5.1: 7,717.6 msecs, correct answer: 1a3099aa " + "Puzzle 5.1: 7,316.0 milliseconds, correct answer: 1a3099aa " ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -704,7 +670,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": { "collapsed": false, "jupyter": { @@ -729,10 +695,10 @@ { "data": { "text/plain": [ - "Puzzle 5.2: 11,693.9 msecs, correct answer: 694190cd " + "Puzzle 5.2: 11,520.3 milliseconds, correct answer: 694190cd " ] }, - "execution_count": 12, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -766,7 +732,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": { "collapsed": false, "jupyter": { @@ -777,10 +743,10 @@ { "data": { "text/plain": [ - "Puzzle 6.1: 0.5 msecs, correct answer: dzqckwsd " + "Puzzle 6.1: 0.5 milliseconds, correct answer: dzqckwsd " ] }, - "execution_count": 13, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -805,7 +771,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": { "collapsed": false, "jupyter": { @@ -816,10 +782,10 @@ { "data": { "text/plain": [ - "Puzzle 6.2: 0.4 msecs, correct answer: lragovly " + "Puzzle 6.2: 0.4 milliseconds, correct answer: lragovly " ] }, - "execution_count": 14, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -852,7 +818,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": { "collapsed": false, "jupyter": { @@ -863,10 +829,10 @@ { "data": { "text/plain": [ - "Puzzle 7.1: 12.7 msecs, correct answer: 118 " + "Puzzle 7.1: 12.4 milliseconds, correct answer: 118 " ] }, - "execution_count": 15, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -892,7 +858,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": { "collapsed": false, "jupyter": { @@ -919,7 +885,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": { "collapsed": false, "jupyter": { @@ -930,10 +896,10 @@ { "data": { "text/plain": [ - "Puzzle 7.2: 118.2 msecs, correct answer: 260 " + "Puzzle 7.2: 119.6 milliseconds, correct answer: 260 " ] }, - "execution_count": 17, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -969,7 +935,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": { "collapsed": false, "jupyter": { @@ -980,10 +946,10 @@ { "data": { "text/plain": [ - "Puzzle 8.1: 0.7 msecs, correct answer: 110 " + "Puzzle 8.1: 0.7 milliseconds, correct answer: 110 " ] }, - "execution_count": 18, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -1023,7 +989,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": { "collapsed": false, "jupyter": { @@ -1046,10 +1012,10 @@ { "data": { "text/plain": [ - "Puzzle 8.2: 0.5 msecs, correct answer: ZJHRKCPLYJ " + "Puzzle 8.2: 0.6 milliseconds, correct answer: ZJHRKCPLYJ " ] }, - "execution_count": 19, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -1074,7 +1040,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": { "collapsed": false, "jupyter": { @@ -1085,10 +1051,10 @@ { "data": { "text/plain": [ - "Puzzle 9.1: 0.4 msecs, correct answer: 115118 " + "Puzzle 9.1: 0.3 milliseconds, correct answer: 115118 " ] }, - "execution_count": 20, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -1126,7 +1092,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": { "collapsed": false, "jupyter": { @@ -1137,10 +1103,10 @@ { "data": { "text/plain": [ - "Puzzle 9.2: 1.8 msecs, correct answer: 11107527530 " + "Puzzle 9.2: 1.7 milliseconds, correct answer: 11107527530 " ] }, - "execution_count": 21, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -1177,7 +1143,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -1219,7 +1185,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "metadata": { "collapsed": false, "jupyter": { @@ -1230,10 +1196,10 @@ { "data": { "text/plain": [ - "Puzzle 10.1: 0.8 msecs, correct answer: 56 " + "Puzzle 10.1: 0.6 milliseconds, correct answer: 56 " ] }, - "execution_count": 23, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -1275,7 +1241,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "metadata": { "collapsed": false, "jupyter": { @@ -1286,10 +1252,10 @@ { "data": { "text/plain": [ - "Puzzle 10.2: 0.5 msecs, correct answer: 7847 " + "Puzzle 10.2: 0.8 milliseconds, correct answer: 7847 " ] }, - "execution_count": 24, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -1310,67 +1276,152 @@ "source": [ "# [Day 11](http://adventofcode.com/2016/day/11): Radioisotope Thermoelectric Generators \n", "\n", - "I *knew* my `astar_search` function would come in handy! To search for the shortest path to the goal, I need to provide an initial state of the world, a heuristic function (which estimates how many moves away from the goal a state is), and a function that says what states can be reached by moving the elevator up or down, and carrying some stuff. I will make these choices:\n", - "* The state of the world is represented by the `State` type, which says what floor the elevator is on, and for a tuple of floors, the set of objects that are on the floor. We use frozensets so that a state will be hashable.\n", - "* To figure out what moves can be made, consider both directions for the elevator (up or down); find all combinations of one or two items on each floor, and keep all of those moves, as long as they don't violate the constraint that we can't have a chip on the same floor as an RTG, unless the chip's own RTG is there.\n", - "* To calculate the heuristic, add up the number of floors away each item is from the top floor and divide by two (since a move might carry two items).\n", + "I *knew* my `astar_search` function would come in handy! To search for the shortest path to the goal, I need to provide:\n", + "- an initial state of the world,\n", + "- a heuristic function (which estimates how many moves away from the goal a state is),\n", + "- a function that says what states can be reached by moving the elevator up or down, and carrying some stuff.\n", "\n", - "Here is my input. I'll just read this with my eyes and put it into my code.\n", + "Here is my input. I'll just read this with my eyes and hand-code it. I won't write a parser for it.\n", "\n", - "* The first floor contains a thulium generator, a thulium-compatible microchip, a plutonium generator, and a strontium generator.\n", - "* The second floor contains a plutonium-compatible microchip and a strontium-compatible microchip.\n", - "* The third floor contains a promethium generator, a promethium-compatible microchip, a ruthenium generator, and a ruthenium-compatible microchip.\n", - "* The fourth floor contains nothing relevant." + "* The first floor contains a **thulium generator**, a **thulium-compatible microchip**, a **plutonium generator**, and a **strontium generator**.\n", + "* The second floor contains a **plutonium-compatible microchip** and a **strontium-compatible microchip**.\n", + "* The third floor contains a **promethium generator**, a **promethium-compatible microchip**, a **ruthenium generator**, and a **ruthenium-compatible microchip**.\n", + "* The fourth floor contains nothing relevant.\n", + "\n", + "I initially represented the initial state (and all states) with a set of objects for each floor. That worked fine, and solved Part 1 in 3 seconds. But Part 2 took 4 minutes. So I decided I needed a representation that made the search space smaller. I can do that by taking advantage of the *symmetry* in the problem: what really matters is which microchips are paired with which generators; their names don't matter. For example, my third floor has a promethium generator/microchip pair and a ruthenium microchip/generator pair. If I move either pair, the effect on the puzzle is the same. I shouldn't count moving the promethium pair as a different state than moving the ruthenium pair; they should be the same state.\n", + "\n", + "To figure out the representation, consider the simpler example from AoC:\n", + "\n", + " #3 . . . . . \n", + " #2 . . . LG . \n", + " #1 . HG . . . \n", + " #0 . . HM . LM \n", + "\n", + "I'll list the floors for each object name with a dict of `{name: (microchip_floor, generator_floor)}`:\n", + "\n", + " {'H': (0, 1), 'L': (0, 2)}\n", + "\n", + "We can then drop the names and sort the tuples:\n", + "\n", + " ((0, 1), (0, 2))\n", + "\n", + "we can make that a state:\n", + "\n", + " State(elevator=0, pairs=((0, 1), (0, 2)))\n", + "\n", + "From that initial state we get three possible next states, of which only one is legal:\n", + "\n", + " State(elevator=1, pairs=((0, 2), (1, 1))) # Move the HM up\n", + " State(elevator=1, pairs=((1, 1), (1, 2))) # Move the HM and the LM up; illegal because the HG is on 1\n", + " State(elevator=1, pairs=((0, 1), (1, 2))) # Move the LM up; illegal because the HG is on 1\n", + "\n", + "I'm ready to implement a search with this representation:" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 11.1: 52.7 milliseconds, correct answer: 31 " + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "State = namedtuple('State', 'elevator, floors')\n", + "State = namedtuple('State', 'elevator, pairs')\n", + "FLOORS = {0, 1, 2, 3}\n", "\n", - "def fset(*items): return frozenset(items)\n", + "initial = State(0, tuple(sorted(dict(Th=(0, 0), Pl=(1, 0), St=(1, 0), Pr=(2, 2), Ru=(2, 2)).values())))\n", + "assert initial == State(elevator=0, pairs=((0, 0), (1, 0), (1, 0), (2, 2), (2, 2)))\n", "\n", - "legal_floors = {0, 1, 2, 3}\n", + "def is_legal(state) -> bool:\n", + " \"\"\"State is legal unless a floor has an RTG and also a microchip with no corresponding RTG.\"\"\"\n", + " rtg_floors = {g for (m, g) in state.pairs}\n", + " unconnected_chip_floors = {m for (m, g) in state.pairs if m != g}\n", + " return not (unconnected_chip_floors & rtg_floors)\n", "\n", - "def combos(things):\n", - " \"All subsets of 1 or 2 things.\"\n", - " for s in chain(combinations(things, 1), combinations(things, 2)):\n", - " yield fset(*s)\n", + "def h_to_top(state) -> int:\n", + " \"\"\"An estimate of the number of moves needed to move everything to top.\"\"\"\n", + " return 0 if set(state.pairs) == {(3, 3)} else 1000\n", + " top = max(FLOORS)\n", + " total = sum((top - f) for f in flatten(state.pairs))\n", + " return math.ceil(total / 2) # Can move two items in one move.\n", "\n", - "def moves(state):\n", - " \"All legal states that can be reached in one move from this state\"\n", - " L, floors = state\n", - " for L2 in {L + 1, L - 1} & legal_floors:\n", - " for stuff in combos(floors[L]):\n", - " newfloors = tuple((s | stuff if i == L2 else \n", - " s - stuff if i == state.elevator else \n", - " s)\n", - " for (i, s) in enumerate(state.floors))\n", - " if legal_floor(newfloors[L]) and legal_floor(newfloors[L2]):\n", - " yield State(L2, newfloors)\n", + "def moves(state) -> Iterable[State]:\n", + " \"\"\"The legal moves from this state.\"\"\"\n", + " L, pairs = state\n", + " copy = [list(pair) for pair in pairs] # A mutable copy of the pairs\n", + " positions = [(i, j) for i, pair in enumerate(pairs) for j in (0, 1) \n", + " if pairs[i][j] == L] # All pairs[i][j] positions that are on this floor.\n", + " legal_moves = set()\n", + " for L2 in {L + 1, L - 1} & FLOORS: # Possible floors to move to\n", + " for (i, j), (k, l) in set(combinations_with_replacement(positions, 2)):\n", + " # For all possible moves of 2 things, make the change (on the copy),\n", + " # check if it is legal, and then restore the copy.\n", + " # `combinations_with_replacement` means we also get moves of 1 thing\n", + " copy[i][j] = L2\n", + " copy[k][l] = L2\n", + " if is_legal(State(L2, copy)):\n", + " legal_moves.add(State(L2, tuple(sorted(tuple(pair) for pair in copy))))\n", + " copy[i][j] = pairs[i][j]\n", + " copy[k][l] = pairs[k][l]\n", + " return legal_moves\n", "\n", - "def legal_floor(floor):\n", - " \"Floor is legal if no RTG, or every chip has its corresponding RTG.\"\n", - " rtgs = any(r.endswith('G') for r in floor)\n", - " chips = [c for c in floor if c.endswith('M')]\n", - " return not rtgs or all(generator_for(c) in floor for c in chips)\n", - "\n", - "def generator_for(chip): return chip[0] + 'G'\n", - "\n", - "def h_to_top(state):\n", - " \"An estimate of the number of moves needed to move everything to top.\"\n", - " total = sum(len(floor) * i for (i, floor) in enumerate(reversed(state.floors)))\n", - " return math.ceil(total / 2) # Can move two items in one move." + "answer(11.1, 31, lambda:\n", + " astar_step_count(initial, h_to_top, moves))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The answer we need is the number of elevator moves, which is one less than the path length (because the path includes the initial state)." + "In **Part 2**, we add two more pairs of objects to the ground floor. That is, we add two more `(0, 0)` pairs to the initial state." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Puzzle 11.2: 311.7 milliseconds, correct answer: 55 " + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "part2 = State(elevator=0, pairs=((0, 0), (0, 0), (0, 0), (1, 0), (1, 0), (2, 2), (2, 2)))\n", + "\n", + "answer(11.2, 55, lambda:\n", + " astar_step_count(part2, h_to_top, moves))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That's 12 times faster than the floor-based representation that does not account for symmetry!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Day 12](http://adventofcode.com/2016/day/12): Leonardo's Monorail\n", + "\n", + "This one looks pretty easy: an interpreter for an assembly language with 4 op codes and 4 registers. We start by parsing a line like `\"cpy 1 a\"` into a tuple, `('cpy', 1, 'a')`. Then to `interpret` the code, we set the program counter, `pc`, to 0, and interpret the instruction at `code[0]`, and continue until the `pc` is past the end of the code:" ] }, { @@ -1386,7 +1437,7 @@ { "data": { "text/plain": [ - "Puzzle 11.1: 3,240.6 msecs, correct answer: 31 " + "Puzzle 12.1: 109.3 milliseconds, correct answer: 318007 " ] }, "execution_count": 26, @@ -1394,92 +1445,6 @@ "output_type": "execute_result" } ], - "source": [ - "part1 = State(0, (fset('TG', 'TM', 'PG', 'SG'), fset('PM', 'SM'), fset('pM', 'pG', 'RM', 'RG'), Ø))\n", - "\n", - "def astar_path_length(*args) -> int:\n", - " \"\"\"The number of moves to find the optimal path to the goal\n", - " (the length of the path, not counting the starting state).\"\"\"\n", - " path = astar_search(*args)\n", - " return len(path) - 1\n", - "\n", - "answer(11.1, 31, lambda:\n", - " astar_path_length(part1, h_to_top, moves))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In **part two**, we add four more items. Now, in part one there were 10 items, each of which could be on any of the 4 floors, so that's 410 ≈ 1 million states. Adding 4 more items yields ≈ 260 million states. We won't visit every state, but the run time could be around 100 times longer. I think I'll start this running, take the dog for a walk, and come back to see if it worked." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Puzzle 11.2: 3.9 MINUTES, correct answer: 55 " - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "part2 = State(0, (fset('TG', 'TM', 'PG', 'SG', 'EG', 'EM', 'DG', 'DM'), \n", - " fset('PM', 'SM'), fset('pM', 'pG', 'RM', 'RG'), Ø))\n", - "\n", - "answer(11.2, 55, lambda:\n", - " astar_path_length(part2, h_to_top, moves))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It worked. And it took about 70 times longer. If I wanted to make it more efficient, I would focus on *symmetry*: when there are two symmetric moves, we only need to consider one. For example, in terms of finding the shortest path, it is the same to move, say `{'TG', 'TM'}` or `{'EG', 'EM'}` or `{'DG', 'DM'}` when they are all on the ground floor." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# [Day 12](http://adventofcode.com/2016/day/12): Leonardo's Monorail\n", - "\n", - "This one looks pretty easy: an interpreter for an assembly language with 4 op codes and 4 registers. We start by parsing a line like `\"cpy 1 a\"` into a tuple, `('cpy', 1, 'a')`. Then to `interpret` the code, we set the program counter, `pc`, to 0, and interpret the instruction at `code[0]`, and continue until the `pc` is past the end of the code:" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "Puzzle 12.1: 109.0 msecs, correct answer: 318007 " - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ "def interpret(code, **regs) -> dict:\n", " \"Execute instructions until pc goes off the end; return registers.\"\n", @@ -1515,7 +1480,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 27, "metadata": { "collapsed": false, "jupyter": { @@ -1526,10 +1491,10 @@ { "data": { "text/plain": [ - "Puzzle 12.2: 3,059.1 msecs, correct answer: 9227661 " + "Puzzle 12.2: 3,142.9 milliseconds, correct answer: 9227661 " ] }, - "execution_count": 29, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -1557,7 +1522,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 28, "metadata": { "collapsed": false, "jupyter": { @@ -1568,10 +1533,10 @@ { "data": { "text/plain": [ - "Puzzle 13.1: 0.3 msecs, correct answer: 82 " + "Puzzle 13.1: 0.3 milliseconds, correct answer: 82 " ] }, - "execution_count": 30, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -1589,7 +1554,7 @@ "def open_neighbors(location): return filter(is_open, neighbors4(location))\n", "\n", "answer(13.1, 82, lambda:\n", - " astar_path_length((1, 1), lambda p: cityblock_distance(p, goal), open_neighbors))" + " astar_step_count((1, 1), lambda p: cityblock_distance(p, goal), open_neighbors))" ] }, { @@ -1601,7 +1566,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 29, "metadata": { "collapsed": false, "jupyter": { @@ -1660,7 +1625,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 30, "metadata": { "collapsed": false, "jupyter": { @@ -1671,10 +1636,10 @@ { "data": { "text/plain": [ - "Puzzle 13.2: 0.2 msecs, correct answer: 138 " + "Puzzle 13.2: 0.2 milliseconds, correct answer: 138 " ] }, - "execution_count": 32, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -1717,7 +1682,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 31, "metadata": { "collapsed": false, "jupyter": { @@ -1728,10 +1693,10 @@ { "data": { "text/plain": [ - "Puzzle 14.1: 130.7 msecs, correct answer: 16106 " + "Puzzle 14.1: 125.8 milliseconds, correct answer: 16106 " ] }, - "execution_count": 33, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -1765,7 +1730,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 32, "metadata": { "collapsed": false, "jupyter": { @@ -1776,10 +1741,10 @@ { "data": { "text/plain": [ - "Puzzle 14.2: 14,479.4 msecs, correct answer: 22423 " + "Puzzle 14.2: 14,487.3 milliseconds, correct answer: 22423 " ] }, - "execution_count": 34, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -1814,7 +1779,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 33, "metadata": { "collapsed": false, "jupyter": { @@ -1825,10 +1790,10 @@ { "data": { "text/plain": [ - "Puzzle 15.1: 53.4 msecs, correct answer: 203660 " + "Puzzle 15.1: 49.0 milliseconds, correct answer: 203660 " ] }, - "execution_count": 35, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -1860,16 +1825,16 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 15.2: 571.1 msecs, correct answer: 2408135 " + "Puzzle 15.2: 583.3 milliseconds, correct answer: 2408135 " ] }, - "execution_count": 36, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -1906,7 +1871,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 35, "metadata": { "collapsed": false, "jupyter": { @@ -1917,10 +1882,10 @@ { "data": { "text/plain": [ - "Puzzle 16.1: 0.0 msecs, correct answer: 01110011101111011" + "Puzzle 16.1: 0.0 milliseconds, correct answer: 01110011101111011" ] }, - "execution_count": 37, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -1957,7 +1922,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 36, "metadata": { "collapsed": false, "jupyter": { @@ -1968,10 +1933,10 @@ { "data": { "text/plain": [ - "Puzzle 16.2: 1,532.5 msecs, correct answer: 11001111011000111" + "Puzzle 16.2: 1,549.0 milliseconds, correct answer: 11001111011000111" ] }, - "execution_count": 38, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -1992,7 +1957,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 37, "metadata": { "collapsed": false, "jupyter": { @@ -2003,10 +1968,10 @@ { "data": { "text/plain": [ - "Puzzle 17.1: 0.0 msecs, correct answer: RRRLDRDUDD " + "Puzzle 17.1: 0.0 milliseconds, correct answer: RRRLDRDUDD " ] }, - "execution_count": 39, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -2049,7 +2014,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 38, "metadata": { "collapsed": false, "jupyter": { @@ -2060,10 +2025,10 @@ { "data": { "text/plain": [ - "Puzzle 17.2: 43.6 msecs, correct answer: 706 " + "Puzzle 17.2: 44.1 milliseconds, correct answer: 706 " ] }, - "execution_count": 40, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } @@ -2096,7 +2061,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 39, "metadata": { "collapsed": false, "jupyter": { @@ -2107,10 +2072,10 @@ { "data": { "text/plain": [ - "Puzzle 18.1: 0.6 msecs, correct answer: 1987 " + "Puzzle 18.1: 0.6 milliseconds, correct answer: 1987 " ] }, - "execution_count": 41, + "execution_count": 39, "metadata": {}, "output_type": "execute_result" } @@ -2140,7 +2105,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 40, "metadata": { "collapsed": false, "jupyter": { @@ -2163,7 +2128,7 @@ " '^^.^^^..^^']" ] }, - "execution_count": 42, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -2181,7 +2146,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 41, "metadata": { "collapsed": false, "jupyter": { @@ -2192,10 +2157,10 @@ { "data": { "text/plain": [ - "Puzzle 18.2: 2,019.0 msecs, correct answer: 19984714 " + "Puzzle 18.2: 2,000.0 milliseconds, correct answer: 19984714 " ] }, - "execution_count": 43, + "execution_count": 41, "metadata": {}, "output_type": "execute_result" } @@ -2231,7 +2196,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 42, "metadata": { "collapsed": false, "jupyter": { @@ -2242,10 +2207,10 @@ { "data": { "text/plain": [ - "Puzzle 19.1: 0.0 msecs, correct answer: 1815603 " + "Puzzle 19.1: 0.0 milliseconds, correct answer: 1815603 " ] }, - "execution_count": 44, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } @@ -2280,7 +2245,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 43, "metadata": { "collapsed": false, "jupyter": { @@ -2321,7 +2286,7 @@ "1815603" ] }, - "execution_count": 45, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } @@ -2346,7 +2311,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 44, "metadata": { "collapsed": false, "jupyter": { @@ -2357,10 +2322,10 @@ { "data": { "text/plain": [ - "Puzzle 19.2: 320.8 msecs, correct answer: 1410630 " + "Puzzle 19.2: 313.6 milliseconds, correct answer: 1410630 " ] }, - "execution_count": 46, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } @@ -2405,7 +2370,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 45, "metadata": { "collapsed": false, "jupyter": { @@ -2423,7 +2388,7 @@ " [1235976, 1247936]]" ] }, - "execution_count": 47, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } @@ -2443,7 +2408,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 46, "metadata": { "collapsed": false, "jupyter": { @@ -2454,10 +2419,10 @@ { "data": { "text/plain": [ - "Puzzle 20.1: 0.0 msecs, correct answer: 32259706 " + "Puzzle 20.1: 0.0 milliseconds, correct answer: 32259706 " ] }, - "execution_count": 48, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -2483,7 +2448,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 47, "metadata": { "collapsed": false, "jupyter": { @@ -2494,10 +2459,10 @@ { "data": { "text/plain": [ - "Puzzle 20.2: 0.1 msecs, correct answer: 113 " + "Puzzle 20.2: 0.1 milliseconds, correct answer: 113 " ] }, - "execution_count": 49, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } @@ -2526,7 +2491,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 48, "metadata": { "collapsed": false, "jupyter": { @@ -2537,10 +2502,10 @@ { "data": { "text/plain": [ - "Puzzle 21.1: 0.4 msecs, correct answer: gfdhebac " + "Puzzle 21.1: 0.3 milliseconds, correct answer: gfdhebac " ] }, - "execution_count": 50, + "execution_count": 48, "metadata": {}, "output_type": "execute_result" } @@ -2581,7 +2546,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 49, "metadata": { "collapsed": false, "jupyter": { @@ -2609,7 +2574,7 @@ "'decab'" ] }, - "execution_count": 51, + "execution_count": 49, "metadata": {}, "output_type": "execute_result" } @@ -2640,7 +2605,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 50, "metadata": { "collapsed": false, "jupyter": { @@ -2651,10 +2616,10 @@ { "data": { "text/plain": [ - "Puzzle 21.2: 2,336.8 msecs, correct answer: dhaegfbc " + "Puzzle 21.2: 2,381.0 milliseconds, correct answer: dhaegfbc " ] }, - "execution_count": 52, + "execution_count": 50, "metadata": {}, "output_type": "execute_result" } @@ -2693,7 +2658,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 51, "metadata": { "collapsed": false, "jupyter": { @@ -2704,10 +2669,10 @@ { "data": { "text/plain": [ - "Puzzle 22.1: 130.1 msecs, correct answer: 1045 " + "Puzzle 22.1: 128.9 milliseconds, correct answer: 1045 " ] }, - "execution_count": 53, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -2732,7 +2697,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 52, "metadata": { "collapsed": false, "jupyter": { @@ -2746,7 +2711,7 @@ "(Node(x=35, y=27, size=89, used=0, avail=89, pct=0), 35)" ] }, - "execution_count": 54, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } @@ -2767,7 +2732,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 53, "metadata": {}, "outputs": [], "source": [ @@ -2784,7 +2749,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 54, "metadata": { "collapsed": false, "jupyter": { @@ -2795,10 +2760,10 @@ { "data": { "text/plain": [ - "Puzzle 22.2: 1,271.5 msecs, correct answer: 265 " + "Puzzle 22.2: 1,208.4 milliseconds, correct answer: 265 " ] }, - "execution_count": 56, + "execution_count": 54, "metadata": {}, "output_type": "execute_result" } @@ -2819,7 +2784,7 @@ " yield State(newdatapos, pos)\n", " \n", "answer(22.2, 265, lambda:\n", - " len(astar_search(State((maxx, 0), (empty.x, empty.y)), distance, moves)) - 1)" + " astar_step_count(State((maxx, 0), (empty.x, empty.y)), distance, moves))" ] }, { @@ -2843,7 +2808,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 55, "metadata": { "collapsed": false, "jupyter": { @@ -2854,10 +2819,10 @@ { "data": { "text/plain": [ - "Puzzle 23.1: 7.3 msecs, correct answer: 10661 " + "Puzzle 23.1: 7.2 milliseconds, correct answer: 10661 " ] }, - "execution_count": 57, + "execution_count": 55, "metadata": {}, "output_type": "execute_result" } @@ -2911,7 +2876,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 56, "metadata": {}, "outputs": [ { @@ -2945,7 +2910,7 @@ " 25: 'jnz c -5\\n'}" ] }, - "execution_count": 58, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -2963,7 +2928,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 57, "metadata": {}, "outputs": [ { @@ -2997,7 +2962,7 @@ " (25, 77)]" ] }, - "execution_count": 59, + "execution_count": 57, "metadata": {}, "output_type": "execute_result" } @@ -3040,7 +3005,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 58, "metadata": {}, "outputs": [], "source": [ @@ -3070,21 +3035,21 @@ } }, "source": [ - "Now I just need to replace those instructions 4–9 with a `mul a b d` instruction. But I don't want to change the number of instructions in the program (because of toggles and jumps), so I'll include 5 no-op instruction. I can use `cpy 0 0` for that:" + "Now I just need to replace those instructions 4–9 with a `mul a b d` instruction. But I don't want to change the number of instructions in the program (because of toggles and jumps), so I'll include 5 no-op instructions. I can use `cpy 0 0` as a no-op:" ] }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 59, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Puzzle 23.2: 2.6 msecs, correct answer: 479007221 " + "Puzzle 23.2: 2.6 milliseconds, correct answer: 479007221 " ] }, - "execution_count": 61, + "execution_count": 59, "metadata": {}, "output_type": "execute_result" } @@ -3111,7 +3076,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "That's 40,000 times faster!" + "That's 100,000 times faster!" ] }, { @@ -3125,7 +3090,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 60, "metadata": { "collapsed": false, "jupyter": { @@ -3148,7 +3113,7 @@ " '6': 1})" ] }, - "execution_count": 62, + "execution_count": 60, "metadata": {}, "output_type": "execute_result" } @@ -3167,7 +3132,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 61, "metadata": { "collapsed": false, "jupyter": { @@ -3181,7 +3146,7 @@ "(1, 19)" ] }, - "execution_count": 63, + "execution_count": 61, "metadata": {}, "output_type": "execute_result" } @@ -3200,7 +3165,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 62, "metadata": { "collapsed": false, "jupyter": { @@ -3211,10 +3176,10 @@ { "data": { "text/plain": [ - "Puzzle 24.1: 297.2 msecs, correct answer: 430 " + "Puzzle 24.1: 272.9 milliseconds, correct answer: 430 " ] }, - "execution_count": 64, + "execution_count": 62, "metadata": {}, "output_type": "execute_result" } @@ -3247,7 +3212,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 63, "metadata": { "collapsed": false, "jupyter": { @@ -3258,10 +3223,10 @@ { "data": { "text/plain": [ - "Puzzle 24.2: 975.8 msecs, correct answer: 700 " + "Puzzle 24.2: 987.9 milliseconds, correct answer: 700 " ] }, - "execution_count": 65, + "execution_count": 63, "metadata": {}, "output_type": "execute_result" } @@ -3289,7 +3254,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 64, "metadata": {}, "outputs": [], "source": [ @@ -3320,7 +3285,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 65, "metadata": { "collapsed": false, "jupyter": { @@ -3331,10 +3296,10 @@ { "data": { "text/plain": [ - "Puzzle 25.1: 910.8 msecs, correct answer: 189 " + "Puzzle 25.1: 910.4 milliseconds, correct answer: 189 " ] }, - "execution_count": 67, + "execution_count": 65, "metadata": {}, "output_type": "execute_result" } @@ -3364,69 +3329,69 @@ "source": [ "# Summary\n", "\n", - "I got all the puzzles solved, but some of my code ran slowly: 11.2 took 4 minutes, and here were 9 other puzzles that took over a second of run time. But the median was just 2.6 milliseconds." + "I got all the puzzles solved, but some of my code ran slowly. Originally 11.2 and 23.2 took over 10 minutes between them, but I went back and optimized them and now they're under 1/10 of a second. There are 8 puzzles that still take over a second of run time. But the median is just 2.6 milliseconds." ] }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 66, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Time: sum = 4.7 MINUTES, mean = 5,798.9 msecs, median = 2.6 msecs, max = 3.9 MINUTES\n", + "Time in milliseconds: sum = 47,651.4, mean = 972.5, median = 2.6, max = 14,487.3\n", "\n", - "Puzzle 1.1: 0.5 msecs, correct answer: 262.0 \n", - "Puzzle 1.2: 0.6 msecs, correct answer: 131.0 \n", - "Puzzle 2.1: 0.6 msecs, correct answer: 14894 \n", - "Puzzle 2.2: 0.5 msecs, correct answer: 26B96 \n", - "Puzzle 3.1: 0.2 msecs, correct answer: 869 \n", - "Puzzle 3.2: 0.4 msecs, correct answer: 1544 \n", - "Puzzle 4.1: 4.0 msecs, correct answer: 409147 \n", - "Puzzle 4.2: 1.6 msecs, correct answer: 991 \n", - "Puzzle 5.1: 7,717.6 msecs, correct answer: 1a3099aa \n", - "Puzzle 5.2: 11,693.9 msecs, correct answer: 694190cd \n", - "Puzzle 6.1: 0.5 msecs, correct answer: dzqckwsd \n", - "Puzzle 6.2: 0.4 msecs, correct answer: lragovly \n", - "Puzzle 7.1: 12.7 msecs, correct answer: 118 \n", - "Puzzle 7.2: 118.2 msecs, correct answer: 260 \n", - "Puzzle 8.1: 0.7 msecs, correct answer: 110 \n", - "Puzzle 8.2: 0.5 msecs, correct answer: ZJHRKCPLYJ \n", - "Puzzle 9.1: 0.4 msecs, correct answer: 115118 \n", - "Puzzle 9.2: 1.8 msecs, correct answer: 11107527530 \n", - "Puzzle 10.1: 0.8 msecs, correct answer: 56 \n", - "Puzzle 10.2: 0.5 msecs, correct answer: 7847 \n", - "Puzzle 11.1: 3,240.6 msecs, correct answer: 31 \n", - "Puzzle 11.2: 3.9 MINUTES, correct answer: 55 \n", - "Puzzle 12.1: 109.0 msecs, correct answer: 318007 \n", - "Puzzle 12.2: 3,059.1 msecs, correct answer: 9227661 \n", - "Puzzle 13.1: 0.3 msecs, correct answer: 82 \n", - "Puzzle 13.2: 0.2 msecs, correct answer: 138 \n", - "Puzzle 14.1: 130.7 msecs, correct answer: 16106 \n", - "Puzzle 14.2: 14,479.4 msecs, correct answer: 22423 \n", - "Puzzle 15.1: 53.4 msecs, correct answer: 203660 \n", - "Puzzle 15.2: 571.1 msecs, correct answer: 2408135 \n", - "Puzzle 16.1: 0.0 msecs, correct answer: 01110011101111011\n", - "Puzzle 16.2: 1,532.5 msecs, correct answer: 11001111011000111\n", - "Puzzle 17.1: 0.0 msecs, correct answer: RRRLDRDUDD \n", - "Puzzle 17.2: 43.6 msecs, correct answer: 706 \n", - "Puzzle 18.1: 0.6 msecs, correct answer: 1987 \n", - "Puzzle 18.2: 2,019.0 msecs, correct answer: 19984714 \n", - "Puzzle 19.1: 0.0 msecs, correct answer: 1815603 \n", - "Puzzle 19.2: 320.8 msecs, correct answer: 1410630 \n", - "Puzzle 20.1: 0.0 msecs, correct answer: 32259706 \n", - "Puzzle 20.2: 0.1 msecs, correct answer: 113 \n", - "Puzzle 21.1: 0.4 msecs, correct answer: gfdhebac \n", - "Puzzle 21.2: 2,336.8 msecs, correct answer: dhaegfbc \n", - "Puzzle 22.1: 130.1 msecs, correct answer: 1045 \n", - "Puzzle 22.2: 1,271.5 msecs, correct answer: 265 \n", - "Puzzle 23.1: 7.3 msecs, correct answer: 10661 \n", - "Puzzle 23.2: 2.6 msecs, correct answer: 479007221 \n", - "Puzzle 24.1: 297.2 msecs, correct answer: 430 \n", - "Puzzle 24.2: 975.8 msecs, correct answer: 700 \n", - "Puzzle 25.1: 910.8 msecs, correct answer: 189 \n" + "Puzzle 1.1: 0.4 milliseconds, correct answer: 262 \n", + "Puzzle 1.2: 0.3 milliseconds, correct answer: 131 \n", + "Puzzle 2.1: 0.5 milliseconds, correct answer: 14894 \n", + "Puzzle 2.2: 0.4 milliseconds, correct answer: 26B96 \n", + "Puzzle 3.1: 0.2 milliseconds, correct answer: 869 \n", + "Puzzle 3.2: 0.4 milliseconds, correct answer: 1544 \n", + "Puzzle 4.1: 4.1 milliseconds, correct answer: 409147 \n", + "Puzzle 4.2: 1.7 milliseconds, correct answer: 991 \n", + "Puzzle 5.1: 7,316.0 milliseconds, correct answer: 1a3099aa \n", + "Puzzle 5.2: 11,520.3 milliseconds, correct answer: 694190cd \n", + "Puzzle 6.1: 0.5 milliseconds, correct answer: dzqckwsd \n", + "Puzzle 6.2: 0.4 milliseconds, correct answer: lragovly \n", + "Puzzle 7.1: 12.4 milliseconds, correct answer: 118 \n", + "Puzzle 7.2: 119.6 milliseconds, correct answer: 260 \n", + "Puzzle 8.1: 0.7 milliseconds, correct answer: 110 \n", + "Puzzle 8.2: 0.6 milliseconds, correct answer: ZJHRKCPLYJ \n", + "Puzzle 9.1: 0.3 milliseconds, correct answer: 115118 \n", + "Puzzle 9.2: 1.7 milliseconds, correct answer: 11107527530 \n", + "Puzzle 10.1: 0.6 milliseconds, correct answer: 56 \n", + "Puzzle 10.2: 0.8 milliseconds, correct answer: 7847 \n", + "Puzzle 11.1: 52.7 milliseconds, correct answer: 31 \n", + "Puzzle 11.2: 311.7 milliseconds, correct answer: 55 \n", + "Puzzle 12.1: 109.3 milliseconds, correct answer: 318007 \n", + "Puzzle 12.2: 3,142.9 milliseconds, correct answer: 9227661 \n", + "Puzzle 13.1: 0.3 milliseconds, correct answer: 82 \n", + "Puzzle 13.2: 0.2 milliseconds, correct answer: 138 \n", + "Puzzle 14.1: 125.8 milliseconds, correct answer: 16106 \n", + "Puzzle 14.2: 14,487.3 milliseconds, correct answer: 22423 \n", + "Puzzle 15.1: 49.0 milliseconds, correct answer: 203660 \n", + "Puzzle 15.2: 583.3 milliseconds, correct answer: 2408135 \n", + "Puzzle 16.1: 0.0 milliseconds, correct answer: 01110011101111011\n", + "Puzzle 16.2: 1,549.0 milliseconds, correct answer: 11001111011000111\n", + "Puzzle 17.1: 0.0 milliseconds, correct answer: RRRLDRDUDD \n", + "Puzzle 17.2: 44.1 milliseconds, correct answer: 706 \n", + "Puzzle 18.1: 0.6 milliseconds, correct answer: 1987 \n", + "Puzzle 18.2: 2,000.0 milliseconds, correct answer: 19984714 \n", + "Puzzle 19.1: 0.0 milliseconds, correct answer: 1815603 \n", + "Puzzle 19.2: 313.6 milliseconds, correct answer: 1410630 \n", + "Puzzle 20.1: 0.0 milliseconds, correct answer: 32259706 \n", + "Puzzle 20.2: 0.1 milliseconds, correct answer: 113 \n", + "Puzzle 21.1: 0.3 milliseconds, correct answer: gfdhebac \n", + "Puzzle 21.2: 2,381.0 milliseconds, correct answer: dhaegfbc \n", + "Puzzle 22.1: 128.9 milliseconds, correct answer: 1045 \n", + "Puzzle 22.2: 1,208.4 milliseconds, correct answer: 265 \n", + "Puzzle 23.1: 7.2 milliseconds, correct answer: 10661 \n", + "Puzzle 23.2: 2.6 milliseconds, correct answer: 479007221 \n", + "Puzzle 24.1: 272.9 milliseconds, correct answer: 430 \n", + "Puzzle 24.2: 987.9 milliseconds, correct answer: 700 \n", + "Puzzle 25.1: 910.4 milliseconds, correct answer: 189 \n" ] } ],